From 16076d25f5503517f9281dea45aba6b1aa1db8aa Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 10:41:35 +0200 Subject: [PATCH 1/8] Add prettier and eslint setup --- .eslintignore | 1 - .eslintrc.json | 75 --- eslint.config.mjs | 129 ++++ examples/lox/src/cli/cli-util.ts | 71 +- examples/lox/src/cli/main.ts | 51 +- examples/lox/src/extension/main.ts | 43 +- examples/lox/src/language/lox-linker.ts | 67 +- examples/lox/src/language/lox-module.ts | 67 +- examples/lox/src/language/lox-scope.ts | 42 +- .../lox/src/language/lox-type-checking.ts | 469 +++++++++---- examples/lox/src/language/lox-utils.ts | 2 +- examples/lox/src/language/lox-validator.ts | 29 +- examples/lox/src/language/main.ts | 11 +- examples/lox/syntaxes/lox.monarch.ts | 79 ++- .../test/lox-type-checking-classes.test.ts | 224 ++++--- .../lox/test/lox-type-checking-cycles.test.ts | 482 +++++++++----- .../test/lox-type-checking-functions.test.ts | 145 ++-- .../lox/test/lox-type-checking-method.test.ts | 136 ++-- .../test/lox-type-checking-operators.test.ts | 88 +-- .../test/lox-type-checking-statements.test.ts | 126 ++-- examples/lox/test/lox-type-checking-utils.ts | 69 +- examples/ox/src/cli/cli-util.ts | 71 +- examples/ox/src/cli/main.ts | 51 +- examples/ox/src/extension/main.ts | 44 +- examples/ox/src/language/main.ts | 11 +- examples/ox/src/language/ox-module.ts | 55 +- examples/ox/src/language/ox-type-checking.ts | 255 +++++-- examples/ox/src/language/ox-validator.ts | 92 ++- .../test/ox-type-checking-functions.test.ts | 44 +- .../test/ox-type-checking-operators.test.ts | 72 +- .../test/ox-type-checking-statements.test.ts | 58 +- examples/ox/test/ox-type-checking-utils.ts | 48 +- package-lock.json | 622 ++++++++++-------- package.json | 7 +- .../src/features/langium-caching.ts | 36 +- .../src/features/langium-inference.ts | 48 +- .../src/features/langium-language.ts | 15 +- .../src/features/langium-printing.ts | 13 +- .../src/features/langium-type-creator.ts | 84 ++- .../src/features/langium-validation.ts | 161 +++-- packages/typir-langium/src/index.ts | 16 +- packages/typir-langium/src/typir-langium.ts | 102 ++- .../src/utils/typir-langium-utils.ts | 13 +- packages/typir/src/graph/graph-algorithms.ts | 78 ++- packages/typir/src/graph/type-edge.ts | 10 +- packages/typir/src/graph/type-graph.ts | 80 ++- packages/typir/src/graph/type-node.ts | 211 +++--- packages/typir/src/index-test.ts | 2 +- packages/typir/src/index.ts | 94 +-- .../src/initialization/type-initializer.ts | 12 +- .../src/initialization/type-reference.ts | 88 ++- .../typir/src/initialization/type-selector.ts | 85 ++- .../typir/src/initialization/type-waiting.ts | 161 +++-- .../typir/src/kinds/bottom/bottom-kind.ts | 98 ++- .../typir/src/kinds/bottom/bottom-type.ts | 39 +- .../src/kinds/class/class-initializer.ts | 319 +++++++-- packages/typir/src/kinds/class/class-kind.ts | 296 ++++++--- packages/typir/src/kinds/class/class-type.ts | 239 ++++--- .../typir/src/kinds/class/class-validation.ts | 162 +++-- .../typir/src/kinds/class/top-class-kind.ts | 68 +- .../typir/src/kinds/class/top-class-type.ts | 45 +- .../fixed-parameters/fixed-parameters-kind.ts | 88 ++- .../fixed-parameters/fixed-parameters-type.ts | 92 ++- .../kinds/function/function-inference-call.ts | 103 ++- .../function/function-inference-overloaded.ts | 114 +++- .../kinds/function/function-initializer.ts | 123 +++- .../typir/src/kinds/function/function-kind.ts | 231 +++++-- .../kinds/function/function-overloading.ts | 119 +++- .../typir/src/kinds/function/function-type.ts | 160 +++-- .../function/function-validation-calls.ts | 183 ++++-- .../function/function-validation-unique.ts | 50 +- packages/typir/src/kinds/kind.ts | 8 +- .../kinds/multiplicity/multiplicity-kind.ts | 79 ++- .../kinds/multiplicity/multiplicity-type.ts | 92 ++- .../src/kinds/primitive/primitive-kind.ts | 106 ++- .../src/kinds/primitive/primitive-type.ts | 49 +- packages/typir/src/kinds/top/top-kind.ts | 92 ++- packages/typir/src/kinds/top/top-type.ts | 39 +- packages/typir/src/services/assignability.ts | 79 ++- packages/typir/src/services/caching.ts | 123 +++- packages/typir/src/services/conversion.ts | 131 ++-- packages/typir/src/services/equality.ts | 84 ++- packages/typir/src/services/inference.ts | 308 ++++++--- packages/typir/src/services/kind-registry.ts | 24 +- packages/typir/src/services/language.ts | 7 +- packages/typir/src/services/operator.ts | 374 ++++++++--- packages/typir/src/services/printing.ts | 94 ++- packages/typir/src/services/subtype.ts | 85 ++- packages/typir/src/services/validation.ts | 524 +++++++++++---- .../src/test/predefined-language-nodes.ts | 110 ++-- packages/typir/src/typir.ts | 172 +++-- .../typir/src/utils/dependency-injection.ts | 90 ++- packages/typir/src/utils/rule-registration.ts | 146 ++-- packages/typir/src/utils/test-utils.ts | 233 +++++-- packages/typir/src/utils/utils-definitions.ts | 175 +++-- .../typir/src/utils/utils-type-comparison.ts | 158 +++-- packages/typir/src/utils/utils.ts | 38 +- packages/typir/test/api-example.test.ts | 151 +++-- packages/typir/test/kinds/class/class.test.ts | 150 +++-- .../function/operator-inference-call.test.ts | 201 ++++-- .../function/operator-overloaded.test.ts | 269 +++++--- ...operator-validation-call-arguments.test.ts | 156 ++++- .../test/kinds/primitive/primitive.test.ts | 100 ++- .../typir/test/services/conversion.test.ts | 35 +- .../test/services/inference-registry.test.ts | 169 +++-- .../test/services/validation-registry.test.ts | 342 ++++++---- packages/typir/test/type-definitions.test.ts | 197 ++++-- packages/typir/test/utils/test-utils.test.ts | 132 ++-- scripts/update-version.js | 2 +- vite.config.ts | 16 +- 110 files changed, 9090 insertions(+), 3924 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json create mode 100644 eslint.config.mjs diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4c43fe68..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -*.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 906ee788..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parserOptions": { - "ecmaVersion": 2017, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint", - "header" - ], - "ignorePatterns": [ - "**/{node_modules,lib,bin}" - ], - "rules": { - // List of [ESLint rules](https://eslint.org/docs/rules/) - "arrow-parens": ["off", "as-needed"], // do not force arrow function parentheses - "constructor-super": "error", // checks the correct use of super() in sub-classes - "dot-notation": "error", // obj.a instead of obj['a'] when possible - "eqeqeq": "error", // ban '==', don't use 'smart' option! - "guard-for-in": "error", // needs obj.hasOwnProperty(key) checks - "new-parens": "error", // new Error() instead of new Error - "no-bitwise": "error", // bitwise operators &, | can be confused with &&, || - "no-caller": "error", // ECMAScript deprecated arguments.caller and arguments.callee - "no-cond-assign": "error", // assignments if (a = '1') are error-prone - "no-debugger": "error", // disallow debugger; statements - "no-eval": "error", // eval is considered unsafe - "no-inner-declarations": "off", // we need to have 'namespace' functions when using TS 'export =' - "no-labels": "error", // GOTO is only used in BASIC ;) - "no-multiple-empty-lines": ["error", {"max": 3}], // two or more empty lines need to be fused to one - "no-new-wrappers": "error", // there is no reason to wrap primitve values - "no-throw-literal": "error", // only throw Error but no objects {} - "no-trailing-spaces": "error", // trim end of lines - "no-unsafe-finally": "error", // safe try/catch/finally behavior - "no-var": "error", // use const and let instead of var - "space-before-function-paren": ["error", { // space in function decl: f() vs async () => {} - "anonymous": "never", - "asyncArrow": "always", - "named": "never" - }], - "semi": [2, "always"], // Always use semicolons at end of statement - "quotes": [2, "single", { "avoidEscape": true }], // Prefer single quotes - "use-isnan": "error", // isNaN(i) Number.isNaN(i) instead of i === NaN - "header/header": [ // Use MIT/Generated file header - 2, - "block", - { "pattern": "MIT License|DO NOT EDIT MANUALLY!" } - ], - // List of [@typescript-eslint rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) - "@typescript-eslint/adjacent-overload-signatures": "error", // grouping same method names - "@typescript-eslint/array-type": ["error", { // string[] instead of Array - "default": "array-simple" - }], - "@typescript-eslint/ban-types": "error", // bans types like String in favor of string - "@typescript-eslint/no-inferrable-types": "off", // don't blame decls like "index: number = 0", esp. in api signatures! - "@typescript-eslint/indent": "error", // consistent indentation - //"@typescript-eslint/no-explicit-any": "error", // don't use :any type - "@typescript-eslint/no-misused-new": "error", // no constructors for interfaces or new for classes - "@typescript-eslint/no-namespace": "off", // disallow the use of custom TypeScript modules and namespaces - "@typescript-eslint/no-non-null-assertion": "off", // allow ! operator - "@typescript-eslint/parameter-properties": "error", // no property definitions in class constructors - "@typescript-eslint/no-unused-vars": ["error", { // disallow Unused Variables - "argsIgnorePattern": "^_" - }], - "@typescript-eslint/no-var-requires": "error", // use import instead of require - "@typescript-eslint/prefer-for-of": "error", // prefer for-of loop over arrays - "@typescript-eslint/prefer-namespace-keyword": "error", // prefer namespace over module in TypeScript - "@typescript-eslint/triple-slash-reference": "error", // ban /// , prefer imports - "@typescript-eslint/type-annotation-spacing": "error" // consistent space around colon ':' - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..f25ee980 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,129 @@ +import { defineConfig } from "eslint/config"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import header from "eslint-plugin-header"; +import tsParser from "@typescript-eslint/parser"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import js from "@eslint/js"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default defineConfig([ + { + ignores: [ + "**/\\{node_modules,lib,bin}", + "**/*.js", + "**/*.cjs", + "packages/typir/lib/**", + ], + }, + { + files: ["**/*.ts"], + extends: compat.extends( + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + ), + + plugins: { + "@typescript-eslint": typescriptEslint, + header, + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 2017, + sourceType: "module", + }, + + rules: { + "arrow-parens": ["off", "as-needed"], + "constructor-super": "error", + "dot-notation": "error", + eqeqeq: "error", + "guard-for-in": "error", + "new-parens": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-cond-assign": "error", + "no-debugger": "error", + "no-eval": "error", + "no-inner-declarations": "off", + "no-labels": "error", + + "no-multiple-empty-lines": [ + "error", + { + max: 3, + }, + ], + + "no-new-wrappers": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-unsafe-finally": "error", + "no-var": "error", + + "space-before-function-paren": [ + "error", + { + anonymous: "never", + asyncArrow: "always", + named: "never", + }, + ], + + semi: [2, "always"], + + quotes: [ + 2, + "single", + { + avoidEscape: true, + }, + ], + + "use-isnan": "error", + + // "header/header": [2, "block", { + // pattern: "MIT License|DO NOT EDIT MANUALLY!", + // }], + + "@typescript-eslint/adjacent-overload-signatures": "error", + + "@typescript-eslint/array-type": [ + "error", + { + default: "array-simple", + }, + ], + + "@typescript-eslint/ban-types": "error", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/indent": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/parameter-properties": "error", + + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + }, + ], + + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/prefer-for-of": "error", + "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/triple-slash-reference": "error", + "@typescript-eslint/type-annotation-spacing": "error", + }, + }, +]); diff --git a/examples/lox/src/cli/cli-util.ts b/examples/lox/src/cli/cli-util.ts index e273640d..b0c5ccf3 100644 --- a/examples/lox/src/cli/cli-util.ts +++ b/examples/lox/src/cli/cli-util.ts @@ -4,17 +4,24 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium'; -import chalk from 'chalk'; -import * as path from 'node:path'; -import * as fs from 'node:fs'; -import { URI } from 'langium'; -import { LangiumServices } from 'langium/lsp'; - -export async function extractDocument(fileName: string, services: LangiumCoreServices): Promise { +import type { AstNode, LangiumCoreServices, LangiumDocument } from "langium"; +import chalk from "chalk"; +import * as path from "node:path"; +import * as fs from "node:fs"; +import { URI } from "langium"; +import { LangiumServices } from "langium/lsp"; + +export async function extractDocument( + fileName: string, + services: LangiumCoreServices, +): Promise { const extensions = services.LanguageMetaData.fileExtensions; if (!extensions.includes(path.extname(fileName))) { - console.error(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`)); + console.error( + chalk.yellow( + `Please choose a file with one of these extensions: ${extensions}.`, + ), + ); process.exit(1); } @@ -23,16 +30,25 @@ export async function extractDocument(fileName: string, services: LangiumCoreSer process.exit(1); } - const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); - await services.shared.workspace.DocumentBuilder.build([document], { validation: true }); + const document = + await services.shared.workspace.LangiumDocuments.getOrCreateDocument( + URI.file(path.resolve(fileName)), + ); + await services.shared.workspace.DocumentBuilder.build([document], { + validation: true, + }); - const validationErrors = (document.diagnostics ?? []).filter(e => e.severity === 1); + const validationErrors = (document.diagnostics ?? []).filter( + (e) => e.severity === 1, + ); if (validationErrors.length > 0) { - console.error(chalk.red('There are validation errors:')); + console.error(chalk.red("There are validation errors:")); for (const validationError of validationErrors) { - console.error(chalk.red( - `line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]` - )); + console.error( + chalk.red( + `line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`, + ), + ); } process.exit(1); } @@ -40,19 +56,28 @@ export async function extractDocument(fileName: string, services: LangiumCoreSer return document; } -export async function extractAstNode(fileName: string, services: LangiumServices): Promise { +export async function extractAstNode( + fileName: string, + services: LangiumServices, +): Promise { return (await extractDocument(fileName, services)).parseResult?.value as T; } interface FilePathData { - destination: string, - name: string + destination: string; + name: string; } -export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathData { - filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, ''); +export function extractDestinationAndName( + filePath: string, + destination: string | undefined, +): FilePathData { + filePath = path + .basename(filePath, path.extname(filePath)) + .replace(/[.-]/g, ""); return { - destination: destination ?? path.join(path.dirname(filePath), 'generated'), - name: path.basename(filePath) + destination: + destination ?? path.join(path.dirname(filePath), "generated"), + name: path.basename(filePath), }; } diff --git a/examples/lox/src/cli/main.ts b/examples/lox/src/cli/main.ts index aee8e19a..07a34fe7 100644 --- a/examples/lox/src/cli/main.ts +++ b/examples/lox/src/cli/main.ts @@ -4,20 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Command } from 'commander'; -import { LoxLanguageMetaData } from '../language/generated/module.js'; -import { extractDestinationAndName } from './cli-util.js'; -import * as url from 'node:url'; -import * as fsp from 'node:fs/promises'; -import * as path from 'node:path'; -import * as fs from 'node:fs'; - -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); - -const packagePath = path.resolve(__dirname, '..', '..', 'package.json'); -const packageContent = await fsp.readFile(packagePath, 'utf-8'); - -export const generateAction = async (fileName: string, opts: GenerateOptions): Promise => { +import { Command } from "commander"; +import { LoxLanguageMetaData } from "../language/generated/module.js"; +import { extractDestinationAndName } from "./cli-util.js"; +import * as url from "node:url"; +import * as fsp from "node:fs/promises"; +import * as path from "node:path"; +import * as fs from "node:fs"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const packagePath = path.resolve(__dirname, "..", "..", "package.json"); +const packageContent = await fsp.readFile(packagePath, "utf-8"); + +export const generateAction = async ( + fileName: string, + opts: GenerateOptions, +): Promise => { // const services = createOxServices(NodeFileSystem).Ox; // const program = await extractAstNode(fileName, services); @@ -29,19 +32,25 @@ export const generateAction = async (fileName: string, opts: GenerateOptions): P export type GenerateOptions = { destination?: string; -} +}; -export default function(): void { +export default function (): void { const program = new Command(); program.version(JSON.parse(packageContent).version); - const fileExtensions = LoxLanguageMetaData.fileExtensions.join(', '); + const fileExtensions = LoxLanguageMetaData.fileExtensions.join(", "); program - .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') - .description('generates from the source file') + .command("generate") + .argument( + "", + `source file (possible file extensions: ${fileExtensions})`, + ) + .option( + "-d, --destination ", + "destination directory of generating", + ) + .description("generates from the source file") .action(generateAction); program.parse(process.argv); diff --git a/examples/lox/src/extension/main.ts b/examples/lox/src/extension/main.ts index a153057a..af6fb1f1 100644 --- a/examples/lox/src/extension/main.ts +++ b/examples/lox/src/extension/main.ts @@ -4,10 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { LanguageClientOptions, ServerOptions} from 'vscode-languageclient/node.js'; -import * as vscode from 'vscode'; -import * as path from 'node:path'; -import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js'; +import type { + LanguageClientOptions, + ServerOptions, +} from "vscode-languageclient/node.js"; +import * as vscode from "vscode"; +import * as path from "node:path"; +import { LanguageClient, TransportKind } from "vscode-languageclient/node.js"; let client: LanguageClient; @@ -25,37 +28,49 @@ export function deactivate(): Thenable | undefined { } function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { - const serverModule = context.asAbsolutePath(path.join('out', 'language', 'main.cjs')); + const serverModule = context.asAbsolutePath( + path.join("out", "language", "main.cjs"), + ); // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached. - const debugOptions = { execArgv: ['--nolazy', `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`] }; + const debugOptions = { + execArgv: [ + "--nolazy", + `--inspect${process.env.DEBUG_BREAK ? "-brk" : ""}=${process.env.DEBUG_SOCKET || "6009"}`, + ], + }; // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used const serverOptions: ServerOptions = { run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.lox'); + const fileSystemWatcher = + vscode.workspace.createFileSystemWatcher("**/*.lox"); context.subscriptions.push(fileSystemWatcher); // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'lox' }], + documentSelector: [{ scheme: "file", language: "lox" }], synchronize: { // Notify the server about file changes to files contained in the workspace - fileEvents: fileSystemWatcher - } + fileEvents: fileSystemWatcher, + }, }; // Create the language client and start the client. const client = new LanguageClient( - 'lox', - 'Lox', + "lox", + "Lox", serverOptions, - clientOptions + clientOptions, ); // Start the client. This will also launch the server diff --git a/examples/lox/src/language/lox-linker.ts b/examples/lox/src/language/lox-linker.ts index 3d2a01cc..b9180136 100644 --- a/examples/lox/src/language/lox-linker.ts +++ b/examples/lox/src/language/lox-linker.ts @@ -4,11 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNodeDescription, DefaultLinker, LinkingError, ReferenceInfo } from 'langium'; -import { isType } from 'typir'; -import { TypirLangiumServices } from 'typir-langium'; -import { isClass, isFunctionDeclaration, isMemberCall, isMethodMember, LoxAstType } from './generated/ast.js'; -import { LoxServices } from './lox-module.js'; +import { + AstNodeDescription, + DefaultLinker, + LinkingError, + ReferenceInfo, +} from "langium"; +import { isType } from "typir"; +import { TypirLangiumServices } from "typir-langium"; +import { + isClass, + isFunctionDeclaration, + isMemberCall, + isMethodMember, + LoxAstType, +} from "./generated/ast.js"; +import { LoxServices } from "./lox-module.js"; export class LoxLinker extends DefaultLinker { protected readonly typir: TypirLangiumServices; @@ -18,29 +29,57 @@ export class LoxLinker extends DefaultLinker { this.typir = services.typir; } - override getCandidate(refInfo: ReferenceInfo): AstNodeDescription | LinkingError { + override getCandidate( + refInfo: ReferenceInfo, + ): AstNodeDescription | LinkingError { const container = refInfo.container; if (isMemberCall(container) && container.explicitOperationCall) { // handle overloaded functions/methods const scope = this.scopeProvider.getScope(refInfo); - const calledDescriptions = scope.getAllElements().filter(d => d.name === refInfo.reference.$refText).toArray(); // same name + const calledDescriptions = scope + .getAllElements() + .filter((d) => d.name === refInfo.reference.$refText) + .toArray(); // same name if (calledDescriptions.length === 1) { return calledDescriptions[0]; // no overloaded functions/methods - } if (calledDescriptions.length >= 2) { + } + if (calledDescriptions.length >= 2) { // in case of overloaded functions/methods, do type inference for given arguments - const argumentTypes = container.arguments.map(arg => this.typir.Inference.inferType(arg)).filter(isType); - if (argumentTypes.length === container.arguments.length) { // for all given arguments, a type is inferred + const argumentTypes = container.arguments + .map((arg) => this.typir.Inference.inferType(arg)) + .filter(isType); + if (argumentTypes.length === container.arguments.length) { + // for all given arguments, a type is inferred for (const calledDescription of calledDescriptions) { const called = this.loadAstNode(calledDescription); if (isClass(called)) { // special case: call of the constructur, without any arguments/parameters return calledDescription; // there is only one constructor without any parameters } - if ((isMethodMember(called) || isFunctionDeclaration(called)) && called.parameters.length === container.arguments.length) { // same number of arguments + if ( + (isMethodMember(called) || + isFunctionDeclaration(called)) && + called.parameters.length === + container.arguments.length + ) { + // same number of arguments // infer expected types of parameters - const parameterTypes = called.parameters.map(p => this.typir.Inference.inferType(p)).filter(isType); - if (parameterTypes.length === called.parameters.length) { // for all parameters, a type is inferred - if (argumentTypes.every((arg, index) => this.typir.Assignability.isAssignable(arg, parameterTypes[index]))) { + const parameterTypes = called.parameters + .map((p) => this.typir.Inference.inferType(p)) + .filter(isType); + if ( + parameterTypes.length === + called.parameters.length + ) { + // for all parameters, a type is inferred + if ( + argumentTypes.every((arg, index) => + this.typir.Assignability.isAssignable( + arg, + parameterTypes[index], + ), + ) + ) { return calledDescription; } } diff --git a/examples/lox/src/language/lox-module.ts b/examples/lox/src/language/lox-module.ts index 9dec4fc4..4a8d13a9 100644 --- a/examples/lox/src/language/lox-module.ts +++ b/examples/lox/src/language/lox-module.ts @@ -4,45 +4,74 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { LangiumSharedCoreServices, Module, PartialLangiumCoreServices, createDefaultCoreModule, inject } from 'langium'; -import { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, createDefaultSharedModule } from 'langium/lsp'; -import { TypirLangiumServices, createTypirLangiumServices, initializeLangiumTypirServices } from 'typir-langium'; -import { LoxAstType, reflection } from './generated/ast.js'; -import { LoxGeneratedModule, LoxGeneratedSharedModule } from './generated/module.js'; -import { LoxLinker } from './lox-linker.js'; -import { LoxScopeProvider } from './lox-scope.js'; -import { LoxTypeSystem } from './lox-type-checking.js'; -import { LoxValidationRegistry, LoxValidator } from './lox-validator.js'; +import { + LangiumSharedCoreServices, + Module, + PartialLangiumCoreServices, + createDefaultCoreModule, + inject, +} from "langium"; +import { + DefaultSharedModuleContext, + LangiumServices, + LangiumSharedServices, + createDefaultSharedModule, +} from "langium/lsp"; +import { + TypirLangiumServices, + createTypirLangiumServices, + initializeLangiumTypirServices, +} from "typir-langium"; +import { LoxAstType, reflection } from "./generated/ast.js"; +import { + LoxGeneratedModule, + LoxGeneratedSharedModule, +} from "./generated/module.js"; +import { LoxLinker } from "./lox-linker.js"; +import { LoxScopeProvider } from "./lox-scope.js"; +import { LoxTypeSystem } from "./lox-type-checking.js"; +import { LoxValidationRegistry, LoxValidator } from "./lox-validator.js"; /** * Declaration of custom services - add your own service classes here. */ export type LoxAddedServices = { validation: { - LoxValidator: LoxValidator, - }, - typir: TypirLangiumServices, // all Langium services are able to access these Typir services for type-checking -} + LoxValidator: LoxValidator; + }; + typir: TypirLangiumServices; // all Langium services are able to access these Typir services for type-checking +}; /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ -export type LoxServices = LangiumServices & LoxAddedServices +export type LoxServices = LangiumServices & LoxAddedServices; /** * Dependency injection module that overrides Langium default services and contributes the * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ -export function createLoxModule(shared: LangiumSharedCoreServices): Module { +export function createLoxModule( + shared: LangiumSharedCoreServices, +): Module { return { validation: { - ValidationRegistry: (services) => new LoxValidationRegistry(services), + ValidationRegistry: (services) => + new LoxValidationRegistry(services), LoxValidator: () => new LoxValidator(), }, // For type checking with Typir, configure the Typir & Typir-Langium services in this way: - typir: () => createTypirLangiumServices(shared, reflection, new LoxTypeSystem(), { /* customize Typir services here */ }), + typir: () => + createTypirLangiumServices( + shared, + reflection, + new LoxTypeSystem(), + { + /* customize Typir services here */ + }, + ), references: { ScopeProvider: (services) => new LoxScopeProvider(services), Linker: (services) => new LoxLinker(services), @@ -66,8 +95,8 @@ export function createLoxModule(shared: LangiumSharedCoreServices): Module e.members); + const allMembers = getClassChain(classItem).flatMap((e) => e.members); return this.createScopeForNodes(allMembers); } } diff --git a/examples/lox/src/language/lox-type-checking.ts b/examples/lox/src/language/lox-type-checking.ts index dd541796..f992e092 100644 --- a/examples/lox/src/language/lox-type-checking.ts +++ b/examples/lox/src/language/lox-type-checking.ts @@ -2,108 +2,251 @@ * Copyright 2024 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ + ******************************************************************************/ -import { AstNode, AstUtils, assertUnreachable } from 'langium'; -import { CreateFieldDetails, CreateMethodDetails, CreateParameterDetails, FunctionType, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand, InferenceRuleNotApplicable, NO_PARAMETER_NAME, TypeInitializer, TypirServices, ValidationProblemAcceptor } from 'typir'; -import { TypirLangiumServices, LangiumTypeSystemDefinition } from 'typir-langium'; -import { BinaryExpression, BooleanLiteral, Class, ForStatement, FunctionDeclaration, IfStatement, LoxAstType, MemberCall, MethodMember, NilLiteral, NumberLiteral, PrintStatement, ReturnStatement, StringLiteral, TypeReference, UnaryExpression, VariableDeclaration, WhileStatement, isClass, isFieldMember, isFunctionDeclaration, isMethodMember, isParameter, isVariableDeclaration } from './generated/ast.js'; +import { AstNode, AstUtils, assertUnreachable } from "langium"; +import { + CreateFieldDetails, + CreateMethodDetails, + CreateParameterDetails, + FunctionType, + InferOperatorWithMultipleOperands, + InferOperatorWithSingleOperand, + InferenceRuleNotApplicable, + NO_PARAMETER_NAME, + TypeInitializer, + TypirServices, + ValidationProblemAcceptor, +} from "typir"; +import { + TypirLangiumServices, + LangiumTypeSystemDefinition, +} from "typir-langium"; +import { + BinaryExpression, + BooleanLiteral, + Class, + ForStatement, + FunctionDeclaration, + IfStatement, + LoxAstType, + MemberCall, + MethodMember, + NilLiteral, + NumberLiteral, + PrintStatement, + ReturnStatement, + StringLiteral, + TypeReference, + UnaryExpression, + VariableDeclaration, + WhileStatement, + isClass, + isFieldMember, + isFunctionDeclaration, + isMethodMember, + isParameter, + isVariableDeclaration, +} from "./generated/ast.js"; /* eslint-disable @typescript-eslint/no-unused-vars */ export class LoxTypeSystem implements LangiumTypeSystemDefinition { - onInitialize(typir: TypirLangiumServices): void { // primitive types // typeBool, typeNumber and typeVoid are specific types for OX, ... - const typeBool = typir.factory.Primitives.create({ primitiveName: 'boolean' }) + const typeBool = typir.factory.Primitives.create({ + primitiveName: "boolean", + }) .inferenceRule({ languageKey: BooleanLiteral }) // this is the more performant notation compared to ... // .inferenceRule({ filter: isBooleanLiteral }) // ... this alternative solution, but they provide the same functionality - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'boolean' }) // this is the more performant notation compared to ... + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "boolean", + }) // this is the more performant notation compared to ... // .inferenceRule({ filter: isTypeReference, matching: node => node.primitive === 'boolean' }) // ... this "easier" notation, but they provide the same functionality .finish(); // ... but their primitive kind is provided/preset by Typir - const typeNumber = typir.factory.Primitives.create({ primitiveName: 'number' }) + const typeNumber = typir.factory.Primitives.create({ + primitiveName: "number", + }) .inferenceRule({ languageKey: NumberLiteral }) - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'number' }) + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "number", + }) .finish(); - const typeString = typir.factory.Primitives.create({ primitiveName: 'string' }) + const typeString = typir.factory.Primitives.create({ + primitiveName: "string", + }) .inferenceRule({ languageKey: StringLiteral }) - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'string' }) + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "string", + }) .finish(); - const typeVoid = typir.factory.Primitives.create({ primitiveName: 'void' }) - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'void' }) + const typeVoid = typir.factory.Primitives.create({ + primitiveName: "void", + }) + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "void", + }) .inferenceRule({ languageKey: PrintStatement }) - .inferenceRule({ languageKey: ReturnStatement, matching: (node: ReturnStatement) => node.value === undefined }) + .inferenceRule({ + languageKey: ReturnStatement, + matching: (node: ReturnStatement) => node.value === undefined, + }) .finish(); - const typeNil = typir.factory.Primitives.create({ primitiveName: 'nil' }) + const typeNil = typir.factory.Primitives.create({ + primitiveName: "nil", + }) .inferenceRule({ languageKey: NilLiteral }) .finish(); // 'nil' is only assignable to variables with a class as type in the LOX implementation here const typeAny = typir.factory.Top.create({}).finish(); // extract inference rules, which is possible here thanks to the unified structure of the Langium grammar (but this is not possible in general!) - const binaryInferenceRule: InferOperatorWithMultipleOperands = { + const binaryInferenceRule: InferOperatorWithMultipleOperands< + AstNode, + BinaryExpression + > = { languageKey: BinaryExpression, - matching: (node: BinaryExpression, name: string) => node.operator === name, - operands: (node: BinaryExpression, _name: string) => [node.left, node.right], + matching: (node: BinaryExpression, name: string) => + node.operator === name, + operands: (node: BinaryExpression, _name: string) => [ + node.left, + node.right, + ], validateArgumentsOfCalls: true, }; - const unaryInferenceRule: InferOperatorWithSingleOperand = { + const unaryInferenceRule: InferOperatorWithSingleOperand< + AstNode, + UnaryExpression + > = { languageKey: UnaryExpression, - matching: (node: UnaryExpression, name: string) => node.operator === name, + matching: (node: UnaryExpression, name: string) => + node.operator === name, operand: (node: UnaryExpression, _name: string) => node.value, validateArgumentsOfCalls: true, }; // binary operators: numbers => number - for (const operator of ['-', '*', '/']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeNumber, right: typeNumber, return: typeNumber }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["-", "*", "/"]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeNumber, + right: typeNumber, + return: typeNumber, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } - typir.factory.Operators.createBinary({ name: '+', signatures: [ - { left: typeNumber, right: typeNumber, return: typeNumber }, - { left: typeString, right: typeString, return: typeString }, - { left: typeNumber, right: typeString, return: typeString }, - { left: typeString, right: typeNumber, return: typeString }, - ]}).inferenceRule(binaryInferenceRule).finish(); + typir.factory.Operators.createBinary({ + name: "+", + signatures: [ + { left: typeNumber, right: typeNumber, return: typeNumber }, + { left: typeString, right: typeString, return: typeString }, + { left: typeNumber, right: typeString, return: typeString }, + { left: typeString, right: typeNumber, return: typeString }, + ], + }) + .inferenceRule(binaryInferenceRule) + .finish(); // binary operators: numbers => boolean - for (const operator of ['<', '<=', '>', '>=']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeNumber, right: typeNumber, return: typeBool }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["<", "<=", ">", ">="]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeNumber, + right: typeNumber, + return: typeBool, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // binary operators: booleans => boolean - for (const operator of ['and', 'or']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeBool, right: typeBool, return: typeBool }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["and", "or"]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeBool, + right: typeBool, + return: typeBool, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // ==, != for all data types (the warning for different types is realized below) - for (const operator of ['==', '!=']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeAny, right: typeAny, return: typeBool }}) + for (const operator of ["==", "!="]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { left: typeAny, right: typeAny, return: typeBool }, + }) .inferenceRule({ ...binaryInferenceRule, // show a warning to the user, if something like "3 == false" is compared, since different types already indicate, that the IF condition will be evaluated to false - validation: (node, _operatorName, _operatorType, accept, typir) => typir.validation.Constraints.ensureNodeIsEquals(node.left, node.right, accept, (actual, expected) => ({ - message: `This comparison will always return '${node.operator === '==' ? 'false' : 'true'}' as '${node.left.$cstNode?.text}' and '${node.right.$cstNode?.text}' have the different types '${actual.name}' and '${expected.name}'.`, - languageNode: node, // inside the BinaryExpression ... - languageProperty: 'operator', // ... mark the '==' or '!=' token, i.e. the 'operator' property - severity: 'warning', - // (The use of "node.right" and "node.left" without casting is possible, since the type checks of the given properties for the actual inference rule are reused for the validation.) - })) + validation: ( + node, + _operatorName, + _operatorType, + accept, + typir, + ) => + typir.validation.Constraints.ensureNodeIsEquals( + node.left, + node.right, + accept, + (actual, expected) => ({ + message: `This comparison will always return '${node.operator === "==" ? "false" : "true"}' as '${node.left.$cstNode?.text}' and '${node.right.$cstNode?.text}' have the different types '${actual.name}' and '${expected.name}'.`, + languageNode: node, // inside the BinaryExpression ... + languageProperty: "operator", // ... mark the '==' or '!=' token, i.e. the 'operator' property + severity: "warning", + // (The use of "node.right" and "node.left" without casting is possible, since the type checks of the given properties for the actual inference rule are reused for the validation.) + }), + ), }) .finish(); } // = for SuperType = SubType (Note that this implementation of LOX realized assignments as operators!) - typir.factory.Operators.createBinary({ name: '=', signature: { left: typeAny, right: typeAny, return: typeAny }}) + typir.factory.Operators.createBinary({ + name: "=", + signature: { left: typeAny, right: typeAny, return: typeAny }, + }) .inferenceRule({ ...binaryInferenceRule, // this validation will be checked for each call of this operator! - validation: (node, _opName, _opType, accept, typir) => typir.validation.Constraints.ensureNodeIsAssignable(node.right, node.left, accept, (actual, expected) => ({ - message: `The expression '${node.right.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.left.$cstNode?.text}' with type '${expected.name}'`, - languageProperty: 'value' }))}) + validation: (node, _opName, _opType, accept, typir) => + typir.validation.Constraints.ensureNodeIsAssignable( + node.right, + node.left, + accept, + (actual, expected) => ({ + message: `The expression '${node.right.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.left.$cstNode?.text}' with type '${expected.name}'`, + languageProperty: "value", + }), + ), + }) .finish(); // unary operators - typir.factory.Operators.createUnary({ name: '!', signature: { operand: typeBool, return: typeBool }}).inferenceRule(unaryInferenceRule).finish(); - typir.factory.Operators.createUnary({ name: '-', signature: { operand: typeNumber, return: typeNumber }}).inferenceRule(unaryInferenceRule).finish(); + typir.factory.Operators.createUnary({ + name: "!", + signature: { operand: typeBool, return: typeBool }, + }) + .inferenceRule(unaryInferenceRule) + .finish(); + typir.factory.Operators.createUnary({ + name: "-", + signature: { operand: typeNumber, return: typeNumber }, + }) + .inferenceRule(unaryInferenceRule) + .finish(); // additional inference rules for ... typir.Inference.addInferenceRulesForAstNodes({ @@ -152,10 +295,15 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { }); // check for unique function declarations - typir.factory.Functions.createUniqueFunctionValidation({ registration: { languageKey: FunctionDeclaration }}); + typir.factory.Functions.createUniqueFunctionValidation({ + registration: { languageKey: FunctionDeclaration }, + }); // check for unique class declarations - const uniqueClassValidator = typir.factory.Classes.createUniqueClassValidation({ registration: 'MYSELF' }); + const uniqueClassValidator = + typir.factory.Classes.createUniqueClassValidation({ + registration: "MYSELF", + }); // check for unique method declarations typir.factory.Classes.createUniqueMethodValidation({ isMethodDeclaration: (node) => isMethodMember(node), // MethodMembers could have other $containers? @@ -163,9 +311,13 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { uniqueClassValidator: uniqueClassValidator, registration: { languageKey: MethodMember }, }); - typir.validation.Collector.addValidationRule(uniqueClassValidator, { languageKey: Class }); // TODO this order is important, solve it in a different way! + typir.validation.Collector.addValidationRule(uniqueClassValidator, { + languageKey: Class, + }); // TODO this order is important, solve it in a different way! // check for cycles in super-sub-type relationships - typir.factory.Classes.createNoSuperClassCyclesValidation({ registration: { languageKey: Class } }); + typir.factory.Classes.createNoSuperClassCyclesValidation({ + registration: { languageKey: Class }, + }); } onNewAstNode(node: AstNode, typir: TypirLangiumServices): void { @@ -181,63 +333,99 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { // class types (nominal typing): if (isClass(node)) { const className = node.name; - const classType = typir.factory.Classes - .create({ - className, - superClasses: node.superClass?.ref, // note that type inference is used here - fields: node.members - .filter(isFieldMember) // only Fields, no Methods - .map(f => >{ - name: f.name, - type: f.type, // note that type inference is used here - }), - methods: node.members - .filter(isMethodMember) // only Methods, no Fields - .map(member => >{ type: this.createFunctionDetails(member, typir) }), // same logic as for functions, since the LOX grammar defines them very similar - associatedLanguageNode: node, // this is used by the ScopeProvider to get the corresponding class declaration after inferring the (class) type of an expression - }) + const classType = typir.factory.Classes.create({ + className, + superClasses: node.superClass?.ref, // note that type inference is used here + fields: node.members + .filter(isFieldMember) // only Fields, no Methods + .map( + (f) => + >{ + name: f.name, + type: f.type, // note that type inference is used here + }, + ), + methods: node.members + .filter(isMethodMember) // only Methods, no Fields + .map( + (member) => + >{ + type: this.createFunctionDetails(member, typir), + }, + ), // same logic as for functions, since the LOX grammar defines them very similar + associatedLanguageNode: node, // this is used by the ScopeProvider to get the corresponding class declaration after inferring the (class) type of an expression + }) // inference rule for declaration - .inferenceRuleForClassDeclaration({ languageKey: Class, matching: (languageNode: Class) => languageNode === node}) + .inferenceRuleForClassDeclaration({ + languageKey: Class, + matching: (languageNode: Class) => languageNode === node, + }) // inference rule for constructor calls (i.e. class literals) conforming to the current class - .inferenceRuleForClassLiterals({ // > + .inferenceRuleForClassLiterals({ + // > languageKey: MemberCall, - matching: (languageNode: MemberCall) => isClass(languageNode.element?.ref) && languageNode.element!.ref.name === className && languageNode.explicitOperationCall, - inputValuesForFields: (_languageNode: MemberCall) => new Map(), // values for fields don't matter for nominal typing + matching: (languageNode: MemberCall) => + isClass(languageNode.element?.ref) && + languageNode.element!.ref.name === className && + languageNode.explicitOperationCall, + inputValuesForFields: (_languageNode: MemberCall) => + new Map(), // values for fields don't matter for nominal typing }) - .inferenceRuleForClassLiterals({ // > + .inferenceRuleForClassLiterals({ + // > languageKey: TypeReference, - matching: (languageNode: TypeReference) => isClass(languageNode.reference?.ref) && languageNode.reference!.ref.name === className, - inputValuesForFields: (_languageNode: TypeReference) => new Map(), // values for fields don't matter for nominal typing + matching: (languageNode: TypeReference) => + isClass(languageNode.reference?.ref) && + languageNode.reference!.ref.name === className, + inputValuesForFields: (_languageNode: TypeReference) => + new Map(), // values for fields don't matter for nominal typing }) // inference rule for accessing fields .inferenceRuleForFieldAccess({ languageKey: MemberCall, - matching: (languageNode: MemberCall) => isFieldMember(languageNode.element?.ref) && languageNode.element!.ref.$container === node && !languageNode.explicitOperationCall, - field: (languageNode: MemberCall) => languageNode.element!.ref!.name, + matching: (languageNode: MemberCall) => + isFieldMember(languageNode.element?.ref) && + languageNode.element!.ref.$container === node && + !languageNode.explicitOperationCall, + field: (languageNode: MemberCall) => + languageNode.element!.ref!.name, }) .finish(); // explicitly declare, that 'nil' can be assigned to any Class variable - classType.addListener(type => { - typir.Conversion.markAsConvertible(typir.factory.Primitives.get({ primitiveName: 'nil' })!, type, 'IMPLICIT_EXPLICIT'); + classType.addListener((type) => { + typir.Conversion.markAsConvertible( + typir.factory.Primitives.get({ primitiveName: "nil" })!, + type, + "IMPLICIT_EXPLICIT", + ); }); // The following idea does not work, since variables in LOX have a concrete class type and not an "any class" type: // typir.conversion.markAsConvertible(typeNil, this.classKind.getOrCreateTopClassType({}), 'IMPLICIT_EXPLICIT'); } } - protected createFunctionDetails(node: FunctionDeclaration | MethodMember, typir: TypirLangiumServices): TypeInitializer { - const config = typir.factory.Functions - .create({ - functionName: node.name, - outputParameter: { name: NO_PARAMETER_NAME, type: node.returnType }, - inputParameters: node.parameters.map(p => (>{ name: p.name, type: p.type })), - associatedLanguageNode: node, - }) + protected createFunctionDetails( + node: FunctionDeclaration | MethodMember, + typir: TypirLangiumServices, + ): TypeInitializer { + const config = typir.factory.Functions.create({ + functionName: node.name, + outputParameter: { name: NO_PARAMETER_NAME, type: node.returnType }, + inputParameters: node.parameters.map( + (p) => + >{ + name: p.name, + type: p.type, + }, + ), + associatedLanguageNode: node, + }) // inference rule for function declaration: .inferenceRuleForDeclaration({ languageKey: node.$type, - matching: (languageNode: FunctionDeclaration | MethodMember) => languageNode === node, // only the current function/method declaration matches! + matching: (languageNode: FunctionDeclaration | MethodMember) => + languageNode === node, // only the current function/method declaration matches! }); /** inference rule for funtion/method calls: * - inferring of overloaded functions works only, if the actual arguments have the expected types! @@ -246,17 +434,23 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { if (isFunctionDeclaration(node)) { config.inferenceRuleForCalls({ languageKey: MemberCall, - matching: (languageNode: MemberCall) => isFunctionDeclaration(languageNode.element?.ref) - && languageNode.explicitOperationCall && languageNode.element!.ref === node, - inputArguments: (languageNode: MemberCall) => languageNode.arguments, + matching: (languageNode: MemberCall) => + isFunctionDeclaration(languageNode.element?.ref) && + languageNode.explicitOperationCall && + languageNode.element!.ref === node, + inputArguments: (languageNode: MemberCall) => + languageNode.arguments, validateArgumentsOfFunctionCalls: true, }); } else if (isMethodMember(node)) { config.inferenceRuleForCalls({ languageKey: MemberCall, - matching: (languageNode: MemberCall) => isMethodMember(languageNode.element?.ref) - && languageNode.explicitOperationCall && languageNode.element!.ref === node, - inputArguments: (languageNode: MemberCall) => languageNode.arguments, + matching: (languageNode: MemberCall) => + isMethodMember(languageNode.element?.ref) && + languageNode.explicitOperationCall && + languageNode.element!.ref === node, + inputArguments: (languageNode: MemberCall) => + languageNode.arguments, validateArgumentsOfFunctionCalls: true, }); } else { @@ -265,32 +459,83 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { return config.finish(); } - // Extracting functions for each validation check might improve their readability - protected validateReturnStatement(node: ReturnStatement, accept: ValidationProblemAcceptor, typir: TypirServices): void { - const callableDeclaration: FunctionDeclaration | MethodMember | undefined = AstUtils.getContainerOfType(node, node => isFunctionDeclaration(node) || isMethodMember(node)); - if (callableDeclaration && callableDeclaration.returnType.primitive && callableDeclaration.returnType.primitive !== 'void' && node.value) { + protected validateReturnStatement( + node: ReturnStatement, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ): void { + const callableDeclaration: + | FunctionDeclaration + | MethodMember + | undefined = AstUtils.getContainerOfType( + node, + (node) => isFunctionDeclaration(node) || isMethodMember(node), + ); + if ( + callableDeclaration && + callableDeclaration.returnType.primitive && + callableDeclaration.returnType.primitive !== "void" && + node.value + ) { // the return value must fit to the return type of the function / method - typir.validation.Constraints.ensureNodeIsAssignable(node.value, callableDeclaration.returnType, accept, (actual, expected) => ({ - message: `The expression '${node.value!.$cstNode?.text}' of type '${actual.name}' is not usable as return value for the function '${callableDeclaration.name}' with return type '${expected.name}'.`, - languageProperty: 'value' })); + typir.validation.Constraints.ensureNodeIsAssignable( + node.value, + callableDeclaration.returnType, + accept, + (actual, expected) => ({ + message: `The expression '${node.value!.$cstNode?.text}' of type '${actual.name}' is not usable as return value for the function '${callableDeclaration.name}' with return type '${expected.name}'.`, + languageProperty: "value", + }), + ); } } - protected validateVariableDeclaration(node: VariableDeclaration, accept: ValidationProblemAcceptor, typir: TypirServices): void { - const typeVoid = typir.factory.Primitives.get({ primitiveName: 'void' })!; - typir.validation.Constraints.ensureNodeHasNotType(node, typeVoid, accept, - () => ({ message: "Variable can't be declared with a type 'void'.", languageProperty: 'type' })); - typir.validation.Constraints.ensureNodeIsAssignable(node.value, node, accept, (actual, expected) => ({ - message: `The expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.name}' with type '${expected.name}'`, - languageProperty: 'value' })); + protected validateVariableDeclaration( + node: VariableDeclaration, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ): void { + const typeVoid = typir.factory.Primitives.get({ + primitiveName: "void", + })!; + typir.validation.Constraints.ensureNodeHasNotType( + node, + typeVoid, + accept, + () => ({ + message: "Variable can't be declared with a type 'void'.", + languageProperty: "type", + }), + ); + typir.validation.Constraints.ensureNodeIsAssignable( + node.value, + node, + accept, + (actual, expected) => ({ + message: `The expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.name}' with type '${expected.name}'`, + languageProperty: "value", + }), + ); } - protected validateCondition(node: IfStatement | WhileStatement | ForStatement, accept: ValidationProblemAcceptor, typir: TypirServices): void { - const typeBool = typir.factory.Primitives.get({ primitiveName: 'boolean' })!; - typir.validation.Constraints.ensureNodeIsAssignable(node.condition, typeBool, accept, - () => ({ message: "Conditions need to be evaluated to 'boolean'.", languageProperty: 'condition' })); + protected validateCondition( + node: IfStatement | WhileStatement | ForStatement, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ): void { + const typeBool = typir.factory.Primitives.get({ + primitiveName: "boolean", + })!; + typir.validation.Constraints.ensureNodeIsAssignable( + node.condition, + typeBool, + accept, + () => ({ + message: "Conditions need to be evaluated to 'boolean'.", + languageProperty: "condition", + }), + ); } - } diff --git a/examples/lox/src/language/lox-utils.ts b/examples/lox/src/language/lox-utils.ts index 7bf93cc4..6021d8d0 100644 --- a/examples/lox/src/language/lox-utils.ts +++ b/examples/lox/src/language/lox-utils.ts @@ -4,7 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Class } from './generated/ast.js'; +import { Class } from "./generated/ast.js"; export function getClassChain(classItem: Class): Class[] { const set = new Set(); diff --git a/examples/lox/src/language/lox-validator.ts b/examples/lox/src/language/lox-validator.ts index d34d41cf..1b14be51 100644 --- a/examples/lox/src/language/lox-validator.ts +++ b/examples/lox/src/language/lox-validator.ts @@ -4,9 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; -import { LoxAstType, VariableDeclaration } from './generated/ast.js'; -import type { LoxServices } from './lox-module.js'; +import { + ValidationAcceptor, + ValidationChecks, + ValidationRegistry, +} from "langium"; +import { LoxAstType, VariableDeclaration } from "./generated/ast.js"; +import type { LoxServices } from "./lox-module.js"; /** * Registry for validation checks. @@ -27,14 +31,19 @@ export class LoxValidationRegistry extends ValidationRegistry { * Validations on type level are done by Typir. */ export class LoxValidator { - - checkVariableDeclaration(decl: VariableDeclaration, accept: ValidationAcceptor): void { + checkVariableDeclaration( + decl: VariableDeclaration, + accept: ValidationAcceptor, + ): void { if (!decl.type && !decl.value) { - accept('error', 'Variables require a type hint or an assignment at creation', { - node: decl, - property: 'name' - }); + accept( + "error", + "Variables require a type hint or an assignment at creation", + { + node: decl, + property: "name", + }, + ); } } - } diff --git a/examples/lox/src/language/main.ts b/examples/lox/src/language/main.ts index 3fa6ad7e..24b1283f 100644 --- a/examples/lox/src/language/main.ts +++ b/examples/lox/src/language/main.ts @@ -4,10 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { startLanguageServer } from 'langium/lsp'; -import { NodeFileSystem } from 'langium/node'; -import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js'; -import { createLoxServices } from './lox-module.js'; +import { startLanguageServer } from "langium/lsp"; +import { NodeFileSystem } from "langium/node"; +import { + createConnection, + ProposedFeatures, +} from "vscode-languageserver/node.js"; +import { createLoxServices } from "./lox-module.js"; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); diff --git a/examples/lox/syntaxes/lox.monarch.ts b/examples/lox/syntaxes/lox.monarch.ts index 71aefc03..134d06de 100644 --- a/examples/lox/syntaxes/lox.monarch.ts +++ b/examples/lox/syntaxes/lox.monarch.ts @@ -1,30 +1,81 @@ // Monarch syntax highlighting for the lox language. export default { keywords: [ - 'and','boolean','class','else','false','for','fun','if','nil','number','or','print','return','string','super','this','true','var','void','while' + "and", + "boolean", + "class", + "else", + "false", + "for", + "fun", + "if", + "nil", + "number", + "or", + "print", + "return", + "string", + "super", + "this", + "true", + "var", + "void", + "while", ], operators: [ - '!','!=','*','+',',','-','.','/',':',';','<','<=','=','==','=>','>','>=' + "!", + "!=", + "*", + "+", + ",", + "-", + ".", + "/", + ":", + ";", + "<", + "<=", + "=", + "==", + "=>", + ">", + ">=", ], symbols: /!|!=|\(|\)|\*|\+|,|-|\.|\/|:|;|<|<=|=|==|=>|>|>=|\{|\}/, tokenizer: { initial: [ - { regex: /[_a-zA-Z][\w_]*/, action: { cases: { '@keywords': {"token":"keyword"}, '@default': {"token":"ID"} }} }, - { regex: /[0-9]+(\.[0-9]+)?/, action: {"token":"number"} }, - { regex: /"[^"]*"/, action: {"token":"string"} }, - { include: '@whitespace' }, - { regex: /@symbols/, action: { cases: { '@operators': {"token":"operator"}, '@default': {"token":""} }} }, + { + regex: /[_a-zA-Z][\w_]*/, + action: { + cases: { + "@keywords": { token: "keyword" }, + "@default": { token: "ID" }, + }, + }, + }, + { regex: /[0-9]+(\.[0-9]+)?/, action: { token: "number" } }, + { regex: /"[^"]*"/, action: { token: "string" } }, + { include: "@whitespace" }, + { + regex: /@symbols/, + action: { + cases: { + "@operators": { token: "operator" }, + "@default": { token: "" }, + }, + }, + }, ], whitespace: [ - { regex: /\s+/, action: {"token":"white"} }, - { regex: /\/\*/, action: {"token":"comment","next":"@comment"} }, - { regex: /\/\/[^\n\r]*/, action: {"token":"comment"} }, + { regex: /\s+/, action: { token: "white" } }, + { regex: /\/\*/, action: { token: "comment", next: "@comment" } }, + { regex: /\/\/[^\n\r]*/, action: { token: "comment" } }, ], comment: [ - { regex: /[^/\*]+/, action: {"token":"comment"} }, - { regex: /\*\//, action: {"token":"comment","next":"@pop"} }, - { regex: /[/\*]/, action: {"token":"comment"} }, + { regex: /[^*]+/, action: { token: "comment" } }, + { regex: /\*\//, action: { token: "comment", next: "@pop" } }, + { regex: /[*]/, action: { token: "comment" } }, ], - } + }, }; diff --git a/examples/lox/test/lox-type-checking-classes.test.ts b/examples/lox/test/lox-type-checking-classes.test.ts index 97e3609c..a5d79897 100644 --- a/examples/lox/test/lox-type-checking-classes.test.ts +++ b/examples/lox/test/lox-type-checking-classes.test.ts @@ -4,135 +4,191 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstUtils } from 'langium'; -import { isClassType, isPrimitiveType } from 'typir'; -import { expectToBeType, expectTypirTypes } from 'typir/test'; -import { describe, expect, test } from 'vitest'; -import { isVariableDeclaration, LoxProgram } from '../src/language/generated/ast.js'; -import { loxServices, validateLox } from './lox-type-checking-utils.js'; - -describe('Test type checking for classes', () => { - - test('Class inheritance for assignments: correct', async () => { - await validateLox(` +import { AstUtils } from "langium"; +import { isClassType, isPrimitiveType } from "typir"; +import { expectToBeType, expectTypirTypes } from "typir/test"; +import { describe, expect, test } from "vitest"; +import { + isVariableDeclaration, + LoxProgram, +} from "../src/language/generated/ast.js"; +import { loxServices, validateLox } from "./lox-type-checking-utils.js"; + +describe("Test type checking for classes", () => { + test("Class inheritance for assignments: correct", async () => { + await validateLox( + ` class MyClass1 { name: string age: number } class MyClass2 < MyClass1 {} var v1: MyClass1 = MyClass2(); - `, 0); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + 0, + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - test('Class inheritance for assignments: wrong', async () => { - await validateLox(` + test("Class inheritance for assignments: wrong", async () => { + await validateLox( + ` class MyClass1 { name: string age: number } class MyClass2 < MyClass1 {} var v1: MyClass2 = MyClass1(); - `, 1); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + 1, + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - test('Class fields: correct values', async () => { - await validateLox(` + test("Class fields: correct values", async () => { + await validateLox( + ` class MyClass1 { name: string age: number } var v1: MyClass1 = MyClass1(); v1.name = "Bob"; v1.age = 42; - `, 0); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + 0, + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Class fields: wrong values', async () => { - await validateLox(` + test("Class fields: wrong values", async () => { + await validateLox( + ` class MyClass1 { name: string age: number } var v1: MyClass1 = MyClass1(); v1.name = 42; v1.age = "Bob"; - `, 2); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + 2, + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Classes must be unique by name 2', async () => { - await validateLox(` + test("Classes must be unique by name 2", async () => { + await validateLox( + ` class MyClass1 { } class MyClass1 { } - `, [ - 'Declared classes need to be unique (MyClass1).', - 'Declared classes need to be unique (MyClass1).', - ]); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + [ + "Declared classes need to be unique (MyClass1).", + "Declared classes need to be unique (MyClass1).", + ], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Classes must be unique by name 3', async () => { - await validateLox(` + test("Classes must be unique by name 3", async () => { + await validateLox( + ` class MyClass2 { } class MyClass2 { } class MyClass2 { } - `, [ - 'Declared classes need to be unique (MyClass2).', - 'Declared classes need to be unique (MyClass2).', - 'Declared classes need to be unique (MyClass2).', - ]); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass2'); + `, + [ + "Declared classes need to be unique (MyClass2).", + "Declared classes need to be unique (MyClass2).", + "Declared classes need to be unique (MyClass2).", + ], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass2"); }); - }); -describe('Class literals', () => { - - test('Class literals 1', async () => { - await validateLox(` +describe("Class literals", () => { + test("Class literals 1", async () => { + await validateLox( + ` class MyClass { name: string age: number } var v1 = MyClass(); // constructor call - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass"); }); - test('Class literals 2', async () => { - await validateLox(` + test("Class literals 2", async () => { + await validateLox( + ` class MyClass { name: string age: number } var v1: MyClass = MyClass(); // constructor call - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass"); }); - test('Class literals 3', async () => { - await validateLox(` + test("Class literals 3", async () => { + await validateLox( + ` class MyClass1 {} class MyClass2 {} var v1: boolean = MyClass1() == MyClass2(); // comparing objects with each other - `, [], "This comparison will always return 'false' as 'MyClass1()' and 'MyClass2()' have the different types 'MyClass1' and 'MyClass2'."); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + [], + "This comparison will always return 'false' as 'MyClass1()' and 'MyClass2()' have the different types 'MyClass1' and 'MyClass2'.", + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - test('nil is assignable to any Class', async () => { - await validateLox(` + test("nil is assignable to any Class", async () => { + await validateLox( + ` class MyClass1 {} class MyClass2 {} var v1 = MyClass1(); var v2: MyClass2 = MyClass2(); v1 = nil; v2 = nil; - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - }); -describe('Class field access', () => { - - test('simple class', async () => { - const program = (await validateLox(` +describe("Class field access", () => { + test("simple class", async () => { + const program = ( + await validateLox( + ` class MyClass { name: string age: number } var v1: MyClass = MyClass(); var v2 = v1.name; var v3 = v1.age; - `, [])).parseResult.value as LoxProgram; - checkVariableDeclaration(program, 'v2', 'string'); - checkVariableDeclaration(program, 'v3', 'number'); + `, + [], + ) + ).parseResult.value as LoxProgram; + checkVariableDeclaration(program, "v2", "string"); + checkVariableDeclaration(program, "v3", "number"); }); - test('different classes with switched properties', async () => { - const program = (await validateLox(` + test("different classes with switched properties", async () => { + const program = ( + await validateLox( + ` class MyClass1 { name: string age: number } class MyClass2 { name: number age: string } var v1: MyClass1 = MyClass1(); @@ -141,16 +197,30 @@ describe('Class field access', () => { var v1age = v1.age; var v2name = v2.name; var v2age = v2.age; - `, [])).parseResult.value as LoxProgram; - checkVariableDeclaration(program, 'v1name', 'string'); - checkVariableDeclaration(program, 'v1age', 'number'); - checkVariableDeclaration(program, 'v2name', 'number'); - checkVariableDeclaration(program, 'v2age', 'string'); + `, + [], + ) + ).parseResult.value as LoxProgram; + checkVariableDeclaration(program, "v1name", "string"); + checkVariableDeclaration(program, "v1age", "number"); + checkVariableDeclaration(program, "v2name", "number"); + checkVariableDeclaration(program, "v2age", "string"); }); - function checkVariableDeclaration(program: LoxProgram, name: string, expectedType: 'string'|'number'): void { - const variables = AstUtils.streamAllContents(program).filter(isVariableDeclaration).filter(v => v.name === name).toArray(); + function checkVariableDeclaration( + program: LoxProgram, + name: string, + expectedType: "string" | "number", + ): void { + const variables = AstUtils.streamAllContents(program) + .filter(isVariableDeclaration) + .filter((v) => v.name === name) + .toArray(); expect(variables).toHaveLength(1); - expectToBeType(loxServices.typir.Inference.inferType(variables[0]), isPrimitiveType, inferred => inferred.getName() === expectedType); + expectToBeType( + loxServices.typir.Inference.inferType(variables[0]), + isPrimitiveType, + (inferred) => inferred.getName() === expectedType, + ); } }); diff --git a/examples/lox/test/lox-type-checking-cycles.test.ts b/examples/lox/test/lox-type-checking-cycles.test.ts index ebe9d229..492bca74 100644 --- a/examples/lox/test/lox-type-checking-cycles.test.ts +++ b/examples/lox/test/lox-type-checking-cycles.test.ts @@ -4,35 +4,46 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isClassType, isFunctionType } from 'typir'; -import { expectTypirTypes } from 'typir/test'; -import { describe, test } from 'vitest'; -import { loxServices, operatorNames, validateLox } from './lox-type-checking-utils.js'; - -describe('Cyclic type definitions where a Class is declared and already used', () => { - test('Class with field of its own type', async () => { - await validateLox(` +import { isClassType, isFunctionType } from "typir"; +import { expectTypirTypes } from "typir/test"; +import { describe, test } from "vitest"; +import { + loxServices, + operatorNames, + validateLox, +} from "./lox-type-checking-utils.js"; + +describe("Cyclic type definitions where a Class is declared and already used", () => { + test("Class with field of its own type", async () => { + await validateLox( + ` class Node { children: Node } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'Node'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "Node"); }); - test('Two Classes with fields with the other Class as type', async () => { - await validateLox(` + test("Two Classes with fields with the other Class as type", async () => { + await validateLox( + ` class A { prop1: B } class B { prop2: A } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); }); - test('Three Classes with fields with one of the other Classes as type', async () => { - await validateLox(` + test("Three Classes with fields with one of the other Classes as type", async () => { + await validateLox( + ` class A { prop1: B } @@ -42,12 +53,15 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class C { prop3: A } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B", "C"); }); - test('Three Classes with fields with two of the other Classes as type', async () => { - await validateLox(` + test("Three Classes with fields with two of the other Classes as type", async () => { + await validateLox( + ` class A { prop1: B prop2: C @@ -60,12 +74,15 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop5: A prop6: B } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B", "C"); }); - test('Class with field of its own type and another dependency', async () => { - await validateLox(` + test("Class with field of its own type and another dependency", async () => { + await validateLox( + ` class Node { children: Node other: Another @@ -73,12 +90,15 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class Another { children: Node } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "Node", "Another"); }); - test('Two Classes with a field of its own type and cyclic dependencies to each other', async () => { - await validateLox(` + test("Two Classes with a field of its own type and cyclic dependencies to each other", async () => { + await validateLox( + ` class Node { own: Node other: Another @@ -87,12 +107,15 @@ describe('Cyclic type definitions where a Class is declared and already used', ( own: Another another: Node } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "Node", "Another"); }); - test('Having two declarations for the delayed class A, but only one type A in the type system', async () => { - await validateLox(` + test("Having two declarations for the delayed class A, but only one type A in the type system", async () => { + await validateLox( + ` class A { property1: B // needs to wait for B, since B is defined below } @@ -100,16 +123,20 @@ describe('Cyclic type definitions where a Class is declared and already used', ( property2: B // needs to wait for B, since B is defined below } class B { } - `, [ // Typir works with this, but for LOX these validation errors are produced: - 'Declared classes need to be unique (A).', - 'Declared classes need to be unique (A).', - ]); + `, + [ + // Typir works with this, but for LOX these validation errors are produced: + "Declared classes need to be unique (A).", + "Declared classes need to be unique (A).", + ], + ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); }); - test('Having three declarations for the delayed class A, but only one type A in the type system', async () => { - await validateLox(` + test("Having three declarations for the delayed class A, but only one type A in the type system", async () => { + await validateLox( + ` class A { property1: B // needs to wait for B, since B is defined below } @@ -120,17 +147,21 @@ describe('Cyclic type definitions where a Class is declared and already used', ( property3: B // needs to wait for B, since B is defined below } class B { } - `, [ // Typir works with this, but for LOX these validation errors are produced: - 'Declared classes need to be unique (A).', - 'Declared classes need to be unique (A).', - 'Declared classes need to be unique (A).', - ]); + `, + [ + // Typir works with this, but for LOX these validation errors are produced: + "Declared classes need to be unique (A).", + "Declared classes need to be unique (A).", + "Declared classes need to be unique (A).", + ], + ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); }); - test('Having two declarations for class A waiting for B, while B itself depends on A', async () => { - await validateLox(` + test("Having two declarations for class A waiting for B, while B itself depends on A", async () => { + await validateLox( + ` class A { property1: B // needs to wait for B, since B is defined below } @@ -140,36 +171,56 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class B { property3: A // should be the valid A and not the invalid A } - `, [ // Typir works with this, but for LOX these validation errors are produced: - 'Declared classes need to be unique (A).', - 'Declared classes need to be unique (A).', - ]); + `, + [ + // Typir works with this, but for LOX these validation errors are produced: + "Declared classes need to be unique (A).", + "Declared classes need to be unique (A).", + ], + ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); }); - test('Class with method: cycle with return type', async () => { - await validateLox(` + test("Class with method: cycle with return type", async () => { + await validateLox( + ` class Node { myMethod(input: number): Node {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'Node'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "Node"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Class with method: cycle with input parameter type', async () => { - await validateLox(` + test("Class with method: cycle with input parameter type", async () => { + await validateLox( + ` class Node { myMethod(input: Node): number {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'Node'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "Node"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Two different Classes with the same method (type) should result in only one method type', async () => { - await validateLox(` + test("Two different Classes with the same method (type) should result in only one method type", async () => { + await validateLox( + ` class A { prop1: boolean myMethod(input: number): boolean {} @@ -178,13 +229,21 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop1: number myMethod(input: number): boolean {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Two different Classes depend on each other regarding their methods return type', async () => { - await validateLox(` + test("Two different Classes depend on each other regarding their methods return type", async () => { + await validateLox( + ` class A { prop1: boolean myMethod(input: number): B {} @@ -193,13 +252,22 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop1: number myMethod(input: number): A {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + "myMethod", + ...operatorNames, + ); }); - test('Two different Classes with the same method which has one of these classes as return type', async () => { - await validateLox(` + test("Two different Classes with the same method which has one of these classes as return type", async () => { + await validateLox( + ` class A { prop1: boolean myMethod(input: number): B {} @@ -208,25 +276,41 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop1: number myMethod(input: number): B {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Same delayed function type is used by a function declaration and a method declaration', async () => { - await validateLox(` + test("Same delayed function type is used by a function declaration and a method declaration", async () => { + await validateLox( + ` class A { myMethod(input: number): B {} } fun myMethod(input: number): B {} class B { } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Two class declarations A with the same delayed method which depends on the class B', async () => { - await validateLox(` + test("Two class declarations A with the same delayed method which depends on the class B", async () => { + await validateLox( + ` class A { myMethod(input: number): B {} } @@ -234,30 +318,47 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): B {} } class B { } - `, [ // Typir works with this, but for LOX these validation errors are produced: - 'Declared classes need to be unique (A).', - 'Declared classes need to be unique (A).', - ]); + `, + [ + // Typir works with this, but for LOX these validation errors are produced: + "Declared classes need to be unique (A).", + "Declared classes need to be unique (A).", + ], + ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Mix of dependencies in classes: 1 method and 1 field', async () => { - await validateLox(` + test("Mix of dependencies in classes: 1 method and 1 field", async () => { + await validateLox( + ` class A { myMethod(input: number): B1 {} } class B1 { propB1: A } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B1"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Mix of dependencies in classes: 1 method and 2 fields (order 1)', async () => { - await validateLox(` + test("Mix of dependencies in classes: 1 method and 2 fields (order 1)", async () => { + await validateLox( + ` class B1 { propB1: B2 } @@ -267,13 +368,21 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class A { myMethod(input: number): B1 {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B1", "B2"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('Mix of dependencies in classes: 1 method and 2 fields (order 2)', async () => { - await validateLox(` + test("Mix of dependencies in classes: 1 method and 2 fields (order 2)", async () => { + await validateLox( + ` class A { myMethod(input: number): B1 {} } @@ -283,13 +392,21 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class B2 { propB1: A } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "A", "B1", "B2"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + ...operatorNames, + ); }); - test('The same class is involved into two dependency cycles', async () => { - await validateLox(` + test("The same class is involved into two dependency cycles", async () => { + await validateLox( + ` class A { probA: C1 myMethod(input: number): B1 {} @@ -306,67 +423,121 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class C2 { methodC2(p: A): void {} } - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2', 'C1', 'C2'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', 'methodC1', 'methodC2', ...operatorNames); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "A", + "B1", + "B2", + "C1", + "C2", + ); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myMethod", + "methodC1", + "methodC2", + ...operatorNames, + ); }); - test('Class inheritance and the order of type definitions', async () => { + test("Class inheritance and the order of type definitions", async () => { // the "normal" case: 1st super class, 2nd sub class - await validateLox(` + await validateLox( + ` class MyClass1 {} class MyClass2 < MyClass1 {} - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - test('Class inheritance and the order of type definitions', async () => { + test("Class inheritance and the order of type definitions", async () => { // switching the order of super and sub class works in Langium and in Typir - await validateLox(` + await validateLox( + ` class MyClass2 < MyClass1 {} class MyClass1 {} - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); }); -describe('Test internal validation of Typir for cycles in the class inheritance hierarchy', () => { - test('Three involved classes: 1 -> 2 -> 3 -> 1', async () => { - await validateLox(` +describe("Test internal validation of Typir for cycles in the class inheritance hierarchy", () => { + test("Three involved classes: 1 -> 2 -> 3 -> 1", async () => { + await validateLox( + ` class MyClass1 < MyClass3 { } class MyClass2 < MyClass1 { } class MyClass3 < MyClass2 { } - `, [ - 'Cycles in super-sub-class-relationships are not allowed: MyClass1', - 'Cycles in super-sub-class-relationships are not allowed: MyClass2', - 'Cycles in super-sub-class-relationships are not allowed: MyClass3', - ]); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2', 'MyClass3'); + `, + [ + "Cycles in super-sub-class-relationships are not allowed: MyClass1", + "Cycles in super-sub-class-relationships are not allowed: MyClass2", + "Cycles in super-sub-class-relationships are not allowed: MyClass3", + ], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + "MyClass3", + ); }); - test('Two involved classes: 1 -> 2 -> 1', async () => { - await validateLox(` + test("Two involved classes: 1 -> 2 -> 1", async () => { + await validateLox( + ` class MyClass1 < MyClass2 { } class MyClass2 < MyClass1 { } - `, [ - 'Cycles in super-sub-class-relationships are not allowed: MyClass1', - 'Cycles in super-sub-class-relationships are not allowed: MyClass2', - ]); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); + `, + [ + "Cycles in super-sub-class-relationships are not allowed: MyClass1", + "Cycles in super-sub-class-relationships are not allowed: MyClass2", + ], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "MyClass1", + "MyClass2", + ); }); - test('One involved class: 1 -> 1', async () => { - await validateLox(` + test("One involved class: 1 -> 1", async () => { + await validateLox( + ` class MyClass1 < MyClass1 { } - `, 'Cycles in super-sub-class-relationships are not allowed: MyClass1'); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + "Cycles in super-sub-class-relationships are not allowed: MyClass1", + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); }); -describe('longer LOX examples with classes regarding ordering', () => { +describe("longer LOX examples with classes regarding ordering", () => { // this test case will work after having the support for cyclic type definitions, since it will solve also issues with topological order of type definitions - test('complete with difficult order of classes', async () => { - await validateLox(` + test("complete with difficult order of classes", async () => { + await validateLox( + ` class SuperClass { a: number } @@ -400,12 +571,21 @@ describe('longer LOX examples with classes regarding ordering', () => { // Assigning a subclass to a super class var superType: SuperClass = x; print superType.a; - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "SuperClass", + "SubClass", + "NestedClass", + ); }); - test('complete with easy order of classes', async () => { - await validateLox(` + test("complete with easy order of classes", async () => { + await validateLox( + ` class SuperClass { a: number } @@ -440,7 +620,15 @@ describe('longer LOX examples with classes regarding ordering', () => { // Assigning a subclass to a super class var superType: SuperClass = x; print superType.a; - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isClassType, + "SuperClass", + "SubClass", + "NestedClass", + ); }); }); diff --git a/examples/lox/test/lox-type-checking-functions.test.ts b/examples/lox/test/lox-type-checking-functions.test.ts index 8f568d85..a7faf9ec 100644 --- a/examples/lox/test/lox-type-checking-functions.test.ts +++ b/examples/lox/test/lox-type-checking-functions.test.ts @@ -4,62 +4,124 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertTrue, assertTypirType, isFunctionType, isPrimitiveType, isType } from 'typir'; -import { expectTypirTypes, } from 'typir/test'; -import { describe, expect, test } from 'vitest'; -import { isFunctionDeclaration, isMemberCall, LoxProgram } from '../src/language/generated/ast.js'; -import { loxServices, operatorNames, validateLox } from './lox-type-checking-utils.js'; +import { + assertTrue, + assertTypirType, + isFunctionType, + isPrimitiveType, + isType, +} from "typir"; +import { expectTypirTypes } from "typir/test"; +import { describe, expect, test } from "vitest"; +import { + isFunctionDeclaration, + isMemberCall, + LoxProgram, +} from "../src/language/generated/ast.js"; +import { + loxServices, + operatorNames, + validateLox, +} from "./lox-type-checking-utils.js"; -describe('Test type checking for user-defined functions', () => { - - test('function: return value and return type must match', async () => { - await validateLox('fun myFunction1() : boolean { return true; }', 0); - await validateLox('fun myFunction2() : boolean { return 2; }', - "The expression '2' of type 'number' is not usable as return value for the function 'myFunction2' with return type 'boolean'."); - await validateLox('fun myFunction3() : number { return 2; }', 0); - await validateLox('fun myFunction4() : number { return true; }', - "The expression 'true' of type 'boolean' is not usable as return value for the function 'myFunction4' with return type 'number'."); - expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction1', 'myFunction2', 'myFunction3', 'myFunction4', ...operatorNames); +describe("Test type checking for user-defined functions", () => { + test("function: return value and return type must match", async () => { + await validateLox("fun myFunction1() : boolean { return true; }", 0); + await validateLox( + "fun myFunction2() : boolean { return 2; }", + "The expression '2' of type 'number' is not usable as return value for the function 'myFunction2' with return type 'boolean'.", + ); + await validateLox("fun myFunction3() : number { return 2; }", 0); + await validateLox( + "fun myFunction4() : number { return true; }", + "The expression 'true' of type 'boolean' is not usable as return value for the function 'myFunction4' with return type 'number'.", + ); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myFunction1", + "myFunction2", + "myFunction3", + "myFunction4", + ...operatorNames, + ); }); - test('overloaded function: different return types are not enough', async () => { - await validateLox(` + test("overloaded function: different return types are not enough", async () => { + await validateLox( + ` fun myFunction() : boolean { return true; } fun myFunction() : number { return 2; } - `, [ - 'Declared functions need to be unique (myFunction()).', - 'Declared functions need to be unique (myFunction()).', - ]); - expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); // the types are different nevertheless! + `, + [ + "Declared functions need to be unique (myFunction()).", + "Declared functions need to be unique (myFunction()).", + ], + ); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myFunction", + "myFunction", + ...operatorNames, + ); // the types are different nevertheless! }); - test('overloaded function: different parameter names are not enough', async () => { - await validateLox(` + test("overloaded function: different parameter names are not enough", async () => { + await validateLox( + ` fun myFunction(input: boolean) : boolean { return true; } fun myFunction(other: boolean) : boolean { return true; } - `, [ - 'Declared functions need to be unique (myFunction(boolean)).', - 'Declared functions need to be unique (myFunction(boolean)).', - ]); - expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', ...operatorNames); // but both functions have the same type! + `, + [ + "Declared functions need to be unique (myFunction(boolean)).", + "Declared functions need to be unique (myFunction(boolean)).", + ], + ); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myFunction", + ...operatorNames, + ); // but both functions have the same type! }); - test('overloaded function: but different parameter types are fine', async () => { - await validateLox(` + test("overloaded function: but different parameter types are fine", async () => { + await validateLox( + ` fun myFunction(input: boolean) : boolean { return true; } fun myFunction(input: number) : boolean { return true; } - `, []); - expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); + `, + [], + ); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myFunction", + "myFunction", + ...operatorNames, + ); }); - test('overloaded function: check correct type inference and cross-references', async () => { - const rootNode = (await validateLox(` + test("overloaded function: check correct type inference and cross-references", async () => { + const rootNode = ( + await validateLox( + ` fun myFunction(input: number) : number { return 987; } fun myFunction(input: boolean) : boolean { return true; } myFunction(123); myFunction(false); - `, [])).parseResult.value as LoxProgram; - expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); + `, + [], + ) + ).parseResult.value as LoxProgram; + expectTypirTypes( + loxServices.typir, + isFunctionType, + "myFunction", + "myFunction", + ...operatorNames, + ); // check type inference + cross-reference of the two method calls expect(rootNode.elements).toHaveLength(4); @@ -70,12 +132,12 @@ describe('Test type checking for user-defined functions', () => { assertTrue(isMemberCall(call1Node)); const method1 = call1Node.element?.ref; assertTrue(isFunctionDeclaration(method1)); - expect(method1.returnType.primitive).toBe('number'); + expect(method1.returnType.primitive).toBe("number"); // check type inference const call1Type = loxServices.typir.Inference.inferType(call1Node); expect(isType(call1Type)).toBeTruthy(); assertTypirType(call1Type, isPrimitiveType); - expect(call1Type.getName()).toBe('number'); + expect(call1Type.getName()).toBe("number"); // Call 2 should be boolean const call2Node = rootNode.elements[3]; @@ -83,12 +145,11 @@ describe('Test type checking for user-defined functions', () => { assertTrue(isMemberCall(call2Node)); const method2 = call2Node.element?.ref; assertTrue(isFunctionDeclaration(method2)); - expect(method2.returnType.primitive).toBe('boolean'); + expect(method2.returnType.primitive).toBe("boolean"); // check type inference const call2Type = loxServices.typir.Inference.inferType(call2Node); expect(isType(call2Type)).toBeTruthy(); assertTypirType(call2Type, isPrimitiveType); - expect(call2Type.getName()).toBe('boolean'); + expect(call2Type.getName()).toBe("boolean"); }); - }); diff --git a/examples/lox/test/lox-type-checking-method.test.ts b/examples/lox/test/lox-type-checking-method.test.ts index 0e527c27..59a10117 100644 --- a/examples/lox/test/lox-type-checking-method.test.ts +++ b/examples/lox/test/lox-type-checking-method.test.ts @@ -4,16 +4,31 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertTrue, assertTypirType, isClassType, isFunctionType, isPrimitiveType, isType } from 'typir'; -import { expectTypirTypes } from 'typir/test'; -import { describe, expect, test } from 'vitest'; -import { isMemberCall, isMethodMember, LoxProgram } from '../src/language/generated/ast.js'; -import { loxServices, operatorNames, validateLox } from './lox-type-checking-utils.js'; +import { + assertTrue, + assertTypirType, + isClassType, + isFunctionType, + isPrimitiveType, + isType, +} from "typir"; +import { expectTypirTypes } from "typir/test"; +import { describe, expect, test } from "vitest"; +import { + isMemberCall, + isMethodMember, + LoxProgram, +} from "../src/language/generated/ast.js"; +import { + loxServices, + operatorNames, + validateLox, +} from "./lox-type-checking-utils.js"; -describe('Test type checking for methods of classes', () => { - - test('Class methods: OK', async () => { - await validateLox(` +describe("Test type checking for methods of classes", () => { + test("Class methods: OK", async () => { + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -21,12 +36,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); - `, []); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + [], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Class methods: wrong return value', async () => { - await validateLox(` + test("Class methods: wrong return value", async () => { + await validateLox( + ` class MyClass1 { method1(input: number): number { return true; @@ -34,12 +52,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); - `, 1); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + 1, + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Class methods: method return type does not fit to variable type', async () => { - await validateLox(` + test("Class methods: method return type does not fit to variable type", async () => { + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -47,12 +68,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: boolean = v1.method1(456); - `, 1); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + 1, + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Class methods: value for input parameter does not fit to the type of the input parameter', async () => { - await validateLox(` + test("Class methods: value for input parameter does not fit to the type of the input parameter", async () => { + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -60,12 +84,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(true); - `, 1); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + 1, + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - test('Class methods: methods are not distinguishable', async () => { - await validateLox(` + test("Class methods: methods are not distinguishable", async () => { + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -74,16 +101,18 @@ describe('Test type checking for methods of classes', () => { return true; } } - `, [ // both methods need to be marked: - 'Declared methods need to be unique (class-MyClass1.method1(number)).', - 'Declared methods need to be unique (class-MyClass1.method1(number)).', - ]); - expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); + `, + [ + // both methods need to be marked: + "Declared methods need to be unique (class-MyClass1.method1(number)).", + "Declared methods need to be unique (class-MyClass1.method1(number)).", + ], + ); + expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); }); - }); -describe('Test overloaded methods', () => { +describe("Test overloaded methods", () => { const methodDeclaration = ` class MyClass { method1(input: number): number { @@ -95,14 +124,25 @@ describe('Test overloaded methods', () => { } `; - test('Calls with correct arguments', async () => { - const rootNode = (await validateLox(`${methodDeclaration} + test("Calls with correct arguments", async () => { + const rootNode = ( + await validateLox( + `${methodDeclaration} var v = MyClass(); v.method1(123); v.method1(false); - `, [])).parseResult.value as LoxProgram; - expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); - expectTypirTypes(loxServices.typir, isFunctionType, 'method1', 'method1', ...operatorNames); + `, + [], + ) + ).parseResult.value as LoxProgram; + expectTypirTypes(loxServices.typir, isClassType, "MyClass"); + expectTypirTypes( + loxServices.typir, + isFunctionType, + "method1", + "method1", + ...operatorNames, + ); // check type inference + cross-reference of the two method calls expect(rootNode.elements).toHaveLength(4); @@ -113,12 +153,12 @@ describe('Test overloaded methods', () => { assertTrue(isMemberCall(call1Node)); const method1 = call1Node.element?.ref; assertTrue(isMethodMember(method1)); - expect(method1.returnType.primitive).toBe('number'); + expect(method1.returnType.primitive).toBe("number"); // check type inference const call1Type = loxServices.typir.Inference.inferType(call1Node); expect(isType(call1Type)).toBeTruthy(); assertTypirType(call1Type, isPrimitiveType); - expect(call1Type.getName()).toBe('number'); + expect(call1Type.getName()).toBe("number"); // Call 2 should be boolean const call2Node = rootNode.elements[3]; @@ -126,21 +166,23 @@ describe('Test overloaded methods', () => { assertTrue(isMemberCall(call2Node)); const method2 = call2Node.element?.ref; assertTrue(isMethodMember(method2)); - expect(method2.returnType.primitive).toBe('boolean'); + expect(method2.returnType.primitive).toBe("boolean"); // check type inference const call2Type = loxServices.typir.Inference.inferType(call2Node); expect(isType(call2Type)).toBeTruthy(); assertTypirType(call2Type, isPrimitiveType); - expect(call2Type.getName()).toBe('boolean'); + expect(call2Type.getName()).toBe("boolean"); }); - test('Call with wrong argument', async () => { - await validateLox(`${methodDeclaration} + test("Call with wrong argument", async () => { + await validateLox( + `${methodDeclaration} var v = MyClass(); v.method1("wrong"); // the linker provides an Method here, but the arguments don't match - `, [ - "The given operands for the call of the overload 'method1' don't match", - ]); + `, + [ + "The given operands for the call of the overload 'method1' don't match", + ], + ); }); - }); diff --git a/examples/lox/test/lox-type-checking-operators.test.ts b/examples/lox/test/lox-type-checking-operators.test.ts index 9cf03ea6..b1b9c537 100644 --- a/examples/lox/test/lox-type-checking-operators.test.ts +++ b/examples/lox/test/lox-type-checking-operators.test.ts @@ -4,50 +4,55 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { validateLox } from './lox-type-checking-utils.js'; +import { describe, test } from "vitest"; +import { validateLox } from "./lox-type-checking-utils.js"; -describe('Test type checking for operators', () => { +describe("Test type checking for operators", () => { + test("binary operators", async () => { + await validateLox("var myResult: number = 2 + 3;", 0); + await validateLox("var myResult: number = 2 - 3;", 0); + await validateLox("var myResult: number = 2 * 3;", 0); + await validateLox("var myResult: number = 2 / 3;", 0); - test('binary operators', async () => { - await validateLox('var myResult: number = 2 + 3;', 0); - await validateLox('var myResult: number = 2 - 3;', 0); - await validateLox('var myResult: number = 2 * 3;', 0); - await validateLox('var myResult: number = 2 / 3;', 0); + await validateLox("var myResult: boolean = 2 < 3;", 0); + await validateLox("var myResult: boolean = 2 <= 3;", 0); + await validateLox("var myResult: boolean = 2 > 3;", 0); + await validateLox("var myResult: boolean = 2 >= 3;", 0); - await validateLox('var myResult: boolean = 2 < 3;', 0); - await validateLox('var myResult: boolean = 2 <= 3;', 0); - await validateLox('var myResult: boolean = 2 > 3;', 0); - await validateLox('var myResult: boolean = 2 >= 3;', 0); + await validateLox("var myResult: boolean = true and false;", 0); + await validateLox("var myResult: boolean = true or false;", 0); - await validateLox('var myResult: boolean = true and false;', 0); - await validateLox('var myResult: boolean = true or false;', 0); + await validateLox("var myResult: boolean = 2 == 3;", 0); + await validateLox("var myResult: boolean = 2 != 3;", 0); + await validateLox("var myResult: boolean = true == false;", 0); + await validateLox("var myResult: boolean = true != false;", 0); - await validateLox('var myResult: boolean = 2 == 3;', 0); - await validateLox('var myResult: boolean = 2 != 3;', 0); - await validateLox('var myResult: boolean = true == false;', 0); - await validateLox('var myResult: boolean = true != false;', 0); - - await validateLox('var myResult: boolean = true == 3;', 0, - "This comparison will always return 'false' as 'true' and '3' have the different types 'boolean' and 'number'."); - await validateLox('var myResult: boolean = 2 != false;', 0, - "This comparison will always return 'true' as '2' and 'false' have the different types 'number' and 'boolean'."); + await validateLox( + "var myResult: boolean = true == 3;", + 0, + "This comparison will always return 'false' as 'true' and '3' have the different types 'boolean' and 'number'.", + ); + await validateLox( + "var myResult: boolean = 2 != false;", + 0, + "This comparison will always return 'true' as '2' and 'false' have the different types 'number' and 'boolean'.", + ); }); - test('unary operator: !', async () => { - await validateLox('var myResult: boolean = !true;', 0); - await validateLox('var myResult: boolean = !!true;', 0); - await validateLox('var myResult: boolean = !!!true;', 0); + test("unary operator: !", async () => { + await validateLox("var myResult: boolean = !true;", 0); + await validateLox("var myResult: boolean = !!true;", 0); + await validateLox("var myResult: boolean = !!!true;", 0); }); - test('unary operator: -', async () => { - await validateLox('var myResult: number = -2;', 0); - await validateLox('var myResult: number = --2;', 0); - await validateLox('var myResult: number = ---2;', 0); + test("unary operator: -", async () => { + await validateLox("var myResult: number = -2;", 0); + await validateLox("var myResult: number = --2;", 0); + await validateLox("var myResult: number = ---2;", 0); }); test('overloaded operator "+"', async () => { - await validateLox('var myResult: number = 1 + 2;', 0); + await validateLox("var myResult: number = 1 + 2;", 0); await validateLox('var myResult: string = "a" + "b";', 0); await validateLox('var myResult: string = "a" + 2;', 0); await validateLox('var myResult: string = 1 + "b";', 0); @@ -55,33 +60,32 @@ describe('Test type checking for operators', () => { await validateLox('var myResult: string = "a" + false;', 1); }); - test('use overloaded operators: +', async () => { - await validateLox('var myVar : number = 2 + 3;', 0, 0); + test("use overloaded operators: +", async () => { + await validateLox("var myVar : number = 2 + 3;", 0, 0); await validateLox('var myVar : string = "a" + "b";', 0, 0); await validateLox('var myVar : string = "a" + 3;', 0, 0); await validateLox('var myVar : string = 2 + "b";', 0, 0); }); - test('use overloaded operators: ==', async () => { - await validateLox('var myVar : boolean = true == false;', 0, 0); - await validateLox('var myVar : boolean = 2 == 3;', 0, 0); - await validateLox('var myVar : boolean = true == 3;', 0, 1); - await validateLox('var myVar : boolean = 2 == false;', 0, 1); + test("use overloaded operators: ==", async () => { + await validateLox("var myVar : boolean = true == false;", 0, 0); + await validateLox("var myVar : boolean = 2 == 3;", 0, 0); + await validateLox("var myVar : boolean = true == 3;", 0, 1); + await validateLox("var myVar : boolean = 2 == false;", 0, 1); }); test('Only a single problem with the inner expression, since the type of "*" is always number!', async () => { - await validateLox('var myVar : number = 2 * (2 * false);', [ + await validateLox("var myVar : number = 2 * (2 * false);", [ "While validating the AstNode '(2 * false)', this error is found: The given operands for the call of '*' don't match.", ]); }); test('Two issues in nested expressions, since "*" expects always numbers, while "and" returns always booleans!', async () => { - await validateLox('var myVar : number = 2 * (2 and false);', [ + await validateLox("var myVar : number = 2 * (2 and false);", [ // this is obvious: left and right need to have the same type "While validating the AstNode '(2 and false)', this error is found: The given operands for the call of 'and' don't match.", // '*' supports only numbers for left and right, but the right operand is always boolean as result of the 'and' operator "While validating the AstNode '2 * (2 and false)', this error is found: The given operands for the call of '*' don't match.", ]); }); - }); diff --git a/examples/lox/test/lox-type-checking-statements.test.ts b/examples/lox/test/lox-type-checking-statements.test.ts index 11cb18d1..f8968756 100644 --- a/examples/lox/test/lox-type-checking-statements.test.ts +++ b/examples/lox/test/lox-type-checking-statements.test.ts @@ -4,102 +4,124 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { validateLox } from './lox-type-checking-utils.js'; - -describe('Test type checking for statements and variables in LOX', () => { - - test('multiple nested and', async () => { - await validateLox('var myResult: boolean = true and false;', 0); - await validateLox('var myResult: boolean = true and false and true;', 0); +import { describe, test } from "vitest"; +import { validateLox } from "./lox-type-checking-utils.js"; + +describe("Test type checking for statements and variables in LOX", () => { + test("multiple nested and", async () => { + await validateLox("var myResult: boolean = true and false;", 0); + await validateLox( + "var myResult: boolean = true and false and true;", + 0, + ); }); - test('number assignments', async () => { - await validateLox('var myResult: number = 2;', 0); - await validateLox('var myResult: number = 2 * 3;', 0); - await validateLox('var myResult: number = 2 < 3;', 1); - await validateLox('var myResult: number = true;', 1); + test("number assignments", async () => { + await validateLox("var myResult: number = 2;", 0); + await validateLox("var myResult: number = 2 * 3;", 0); + await validateLox("var myResult: number = 2 < 3;", 1); + await validateLox("var myResult: number = true;", 1); }); - test('boolean assignments', async () => { - await validateLox('var myResult: boolean = true;', 0); - await validateLox('var myResult: boolean = 2;', 1); - await validateLox('var myResult: boolean = 2 * 3;', 1); - await validateLox('var myResult: boolean = 2 < 3;', 0); + test("boolean assignments", async () => { + await validateLox("var myResult: boolean = true;", 0); + await validateLox("var myResult: boolean = 2;", 1); + await validateLox("var myResult: boolean = 2 * 3;", 1); + await validateLox("var myResult: boolean = 2 < 3;", 0); }); - test('statement assignments', async () => { - await validateLox('var myResult: boolean; myResult = true;', 0); - await validateLox('var myResult: boolean; myResult = 2;', 1); - await validateLox('var myResult: boolean; myResult = 2 * 3;', 1); - await validateLox('var myResult: boolean; myResult = 2 < 3;', 0); + test("statement assignments", async () => { + await validateLox("var myResult: boolean; myResult = true;", 0); + await validateLox("var myResult: boolean; myResult = 2;", 1); + await validateLox("var myResult: boolean; myResult = 2 * 3;", 1); + await validateLox("var myResult: boolean; myResult = 2 < 3;", 0); }); - test('boolean in conditions', async () => { - await validateLox('if ( true ) {}', 0); - await validateLox('if ( 3 ) {}', 1); + test("boolean in conditions", async () => { + await validateLox("if ( true ) {}", 0); + await validateLox("if ( 3 ) {}", 1); }); - test('variable declarations', async () => { - await validateLox('var myVar : boolean;', 0); - await validateLox('var myVar : number;', 0); - await validateLox('var myVar : void;', 1); + test("variable declarations", async () => { + await validateLox("var myVar : boolean;", 0); + await validateLox("var myVar : number;", 0); + await validateLox("var myVar : void;", 1); }); - test('Variables without explicit type: assignment', async () => { - await validateLox(` + test("Variables without explicit type: assignment", async () => { + await validateLox( + ` var min = 14; var max = 22; max = min; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: assign expression to var without type', async () => { - await validateLox(` + test("Variables without explicit type: assign expression to var without type", async () => { + await validateLox( + ` var min = 14; var max = 22; var sum = min + max; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: assign expression to var with type', async () => { - await validateLox(` + test("Variables without explicit type: assign expression to var with type", async () => { + await validateLox( + ` var min = 14; var max = 22; var sum : number = min + max; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: assign var again with expression of overloaded operator +', async () => { - await validateLox(` + test("Variables without explicit type: assign var again with expression of overloaded operator +", async () => { + await validateLox( + ` var min = 14; var max = 22; max = min + max; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: assign var again with expression of overloaded operator -', async () => { - await validateLox(` + test("Variables without explicit type: assign var again with expression of overloaded operator -", async () => { + await validateLox( + ` var min = 14; var max = 22; max = min - max; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: assign var again with expression of not overloaded operator *', async () => { - await validateLox(` + test("Variables without explicit type: assign var again with expression of not overloaded operator *", async () => { + await validateLox( + ` var min = 14; var max = 22; max = min * max; - `, 0); + `, + 0, + ); }); - test('Variables without explicit type: used in function', async () => { - await validateLox(` + test("Variables without explicit type: used in function", async () => { + await validateLox( + ` var min = 14; var max = 22; var average = (min + max) / 2; - `, 0); + `, + 0, + ); }); - }); diff --git a/examples/lox/test/lox-type-checking-utils.ts b/examples/lox/test/lox-type-checking-utils.ts index efeb0a78..37cad50b 100644 --- a/examples/lox/test/lox-type-checking-utils.ts +++ b/examples/lox/test/lox-type-checking-utils.ts @@ -4,18 +4,37 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EmptyFileSystem, LangiumDocument } from 'langium'; -import { parseDocument } from 'langium/test'; -import { isClassType, isFunctionType } from 'typir'; -import { deleteAllDocuments } from 'typir-langium'; -import { compareValidationIssuesStrict, expectTypirTypes } from 'typir/test'; -import { afterEach, expect } from 'vitest'; -import type { Diagnostic } from 'vscode-languageserver-types'; -import { DiagnosticSeverity } from 'vscode-languageserver-types'; -import { createLoxServices } from '../src/language/lox-module.js'; +import { EmptyFileSystem, LangiumDocument } from "langium"; +import { parseDocument } from "langium/test"; +import { isClassType, isFunctionType } from "typir"; +import { deleteAllDocuments } from "typir-langium"; +import { compareValidationIssuesStrict, expectTypirTypes } from "typir/test"; +import { afterEach, expect } from "vitest"; +import type { Diagnostic } from "vscode-languageserver-types"; +import { DiagnosticSeverity } from "vscode-languageserver-types"; +import { createLoxServices } from "../src/language/lox-module.js"; export const loxServices = createLoxServices(EmptyFileSystem).Lox; -export const operatorNames = ['-', '*', '/', '+', '+', '+', '+', '<', '<=', '>', '>=', 'and', 'or', '==', '!=', '=', '!', '-']; +export const operatorNames = [ + "-", + "*", + "/", + "+", + "+", + "+", + "+", + "<", + "<=", + ">", + ">=", + "and", + "or", + "==", + "!=", + "=", + "!", + "-", +]; afterEach(async () => { await deleteAllDocuments(loxServices.shared); @@ -24,26 +43,40 @@ afterEach(async () => { expectTypirTypes(loxServices.typir, isFunctionType, ...operatorNames); }); -export async function validateLox(lox: string, errors: number | string | string[], warnings: number | string | string[] = 0): Promise { +export async function validateLox( + lox: string, + errors: number | string | string[], + warnings: number | string | string[] = 0, +): Promise { const document = await parseDocument(loxServices, lox.trim()); - const diagnostics: Diagnostic[] = await loxServices.validation.DocumentValidator.validateDocument(document); + const diagnostics: Diagnostic[] = + await loxServices.validation.DocumentValidator.validateDocument( + document, + ); // errors - const diagnosticsErrors: string[] = diagnostics.filter(d => d.severity === DiagnosticSeverity.Error).map(d => d.message); + const diagnosticsErrors: string[] = diagnostics + .filter((d) => d.severity === DiagnosticSeverity.Error) + .map((d) => d.message); checkIssues(diagnosticsErrors, errors); // warnings - const diagnosticsWarnings: string[] = diagnostics.filter(d => d.severity === DiagnosticSeverity.Warning).map(d => d.message); + const diagnosticsWarnings: string[] = diagnostics + .filter((d) => d.severity === DiagnosticSeverity.Warning) + .map((d) => d.message); checkIssues(diagnosticsWarnings, warnings); return document; } -function checkIssues(diagnosticsErrors: string[], errors: number | string | string[]): void { - const msgError = diagnosticsErrors.join('\n'); - if (typeof errors === 'number') { +function checkIssues( + diagnosticsErrors: string[], + errors: number | string | string[], +): void { + const msgError = diagnosticsErrors.join("\n"); + if (typeof errors === "number") { expect(diagnosticsErrors, msgError).toHaveLength(errors); - } else if (typeof errors === 'string') { + } else if (typeof errors === "string") { expect(diagnosticsErrors, msgError).toHaveLength(1); expect(diagnosticsErrors[0], msgError).includes(errors); } else { diff --git a/examples/ox/src/cli/cli-util.ts b/examples/ox/src/cli/cli-util.ts index 2c2370cd..84036c69 100644 --- a/examples/ox/src/cli/cli-util.ts +++ b/examples/ox/src/cli/cli-util.ts @@ -4,17 +4,24 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium'; -import chalk from 'chalk'; -import * as path from 'node:path'; -import * as fs from 'node:fs'; -import { URI } from 'langium'; -import { LangiumServices } from 'langium/lsp'; - -export async function extractDocument(fileName: string, services: LangiumCoreServices): Promise { +import type { AstNode, LangiumCoreServices, LangiumDocument } from "langium"; +import chalk from "chalk"; +import * as path from "node:path"; +import * as fs from "node:fs"; +import { URI } from "langium"; +import { LangiumServices } from "langium/lsp"; + +export async function extractDocument( + fileName: string, + services: LangiumCoreServices, +): Promise { const extensions = services.LanguageMetaData.fileExtensions; if (!extensions.includes(path.extname(fileName))) { - console.error(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`)); + console.error( + chalk.yellow( + `Please choose a file with one of these extensions: ${extensions}.`, + ), + ); process.exit(1); } @@ -23,16 +30,25 @@ export async function extractDocument(fileName: string, services: LangiumCoreSer process.exit(1); } - const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); - await services.shared.workspace.DocumentBuilder.build([document], { validation: true }); + const document = + await services.shared.workspace.LangiumDocuments.getOrCreateDocument( + URI.file(path.resolve(fileName)), + ); + await services.shared.workspace.DocumentBuilder.build([document], { + validation: true, + }); - const validationErrors = (document.diagnostics ?? []).filter(e => e.severity === 1); + const validationErrors = (document.diagnostics ?? []).filter( + (e) => e.severity === 1, + ); if (validationErrors.length > 0) { - console.error(chalk.red('There are validation errors:')); + console.error(chalk.red("There are validation errors:")); for (const validationError of validationErrors) { - console.error(chalk.red( - `line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]` - )); + console.error( + chalk.red( + `line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`, + ), + ); } process.exit(1); } @@ -40,19 +56,28 @@ export async function extractDocument(fileName: string, services: LangiumCoreSer return document; } -export async function extractAstNode(fileName: string, services: LangiumServices): Promise { +export async function extractAstNode( + fileName: string, + services: LangiumServices, +): Promise { return (await extractDocument(fileName, services)).parseResult?.value as T; } interface FilePathData { - destination: string, - name: string + destination: string; + name: string; } -export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathData { - filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, ''); +export function extractDestinationAndName( + filePath: string, + destination: string | undefined, +): FilePathData { + filePath = path + .basename(filePath, path.extname(filePath)) + .replace(/[.-]/g, ""); return { - destination: destination ?? path.join(path.dirname(filePath), 'generated'), - name: path.basename(filePath) + destination: + destination ?? path.join(path.dirname(filePath), "generated"), + name: path.basename(filePath), }; } diff --git a/examples/ox/src/cli/main.ts b/examples/ox/src/cli/main.ts index 6a5ed0fe..2f6c83ad 100644 --- a/examples/ox/src/cli/main.ts +++ b/examples/ox/src/cli/main.ts @@ -4,20 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Command } from 'commander'; -import { OxLanguageMetaData } from '../language/generated/module.js'; -import { extractDestinationAndName } from './cli-util.js'; -import * as url from 'node:url'; -import * as fsp from 'node:fs/promises'; -import * as path from 'node:path'; -import * as fs from 'node:fs'; - -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); - -const packagePath = path.resolve(__dirname, '..', '..', 'package.json'); -const packageContent = await fsp.readFile(packagePath, 'utf-8'); - -export const generateAction = async (fileName: string, opts: GenerateOptions): Promise => { +import { Command } from "commander"; +import { OxLanguageMetaData } from "../language/generated/module.js"; +import { extractDestinationAndName } from "./cli-util.js"; +import * as url from "node:url"; +import * as fsp from "node:fs/promises"; +import * as path from "node:path"; +import * as fs from "node:fs"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const packagePath = path.resolve(__dirname, "..", "..", "package.json"); +const packageContent = await fsp.readFile(packagePath, "utf-8"); + +export const generateAction = async ( + fileName: string, + opts: GenerateOptions, +): Promise => { // const services = createOxServices(NodeFileSystem).Ox; // const program = await extractAstNode(fileName, services); @@ -29,19 +32,25 @@ export const generateAction = async (fileName: string, opts: GenerateOptions): P export type GenerateOptions = { destination?: string; -} +}; -export default function(): void { +export default function (): void { const program = new Command(); program.version(JSON.parse(packageContent).version); - const fileExtensions = OxLanguageMetaData.fileExtensions.join(', '); + const fileExtensions = OxLanguageMetaData.fileExtensions.join(", "); program - .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') - .description('generates from the source file') + .command("generate") + .argument( + "", + `source file (possible file extensions: ${fileExtensions})`, + ) + .option( + "-d, --destination ", + "destination directory of generating", + ) + .description("generates from the source file") .action(generateAction); program.parse(process.argv); diff --git a/examples/ox/src/extension/main.ts b/examples/ox/src/extension/main.ts index f661e671..9d9ad2d7 100644 --- a/examples/ox/src/extension/main.ts +++ b/examples/ox/src/extension/main.ts @@ -4,10 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { LanguageClientOptions, ServerOptions} from 'vscode-languageclient/node.js'; -import * as vscode from 'vscode'; -import * as path from 'node:path'; -import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js'; +import type { + LanguageClientOptions, + ServerOptions, +} from "vscode-languageclient/node.js"; +import * as vscode from "vscode"; +import * as path from "node:path"; +import { LanguageClient, TransportKind } from "vscode-languageclient/node.js"; let client: LanguageClient; @@ -25,38 +28,45 @@ export function deactivate(): Thenable | undefined { } function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { - const serverModule = context.asAbsolutePath(path.join('out', 'language', 'main.cjs')); + const serverModule = context.asAbsolutePath( + path.join("out", "language", "main.cjs"), + ); // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached. - const debugOptions = { execArgv: ['--nolazy', `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`] }; + const debugOptions = { + execArgv: [ + "--nolazy", + `--inspect${process.env.DEBUG_BREAK ? "-brk" : ""}=${process.env.DEBUG_SOCKET || "6009"}`, + ], + }; // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used const serverOptions: ServerOptions = { run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.ox'); + const fileSystemWatcher = + vscode.workspace.createFileSystemWatcher("**/*.ox"); context.subscriptions.push(fileSystemWatcher); // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'ox' }], + documentSelector: [{ scheme: "file", language: "ox" }], synchronize: { // Notify the server about file changes to files contained in the workspace - fileEvents: fileSystemWatcher - } + fileEvents: fileSystemWatcher, + }, }; // Create the language client and start the client. - const client = new LanguageClient( - 'ox', - 'Ox', - serverOptions, - clientOptions - ); + const client = new LanguageClient("ox", "Ox", serverOptions, clientOptions); // Start the client. This will also launch the server client.start(); diff --git a/examples/ox/src/language/main.ts b/examples/ox/src/language/main.ts index 3bf41dc9..5632be71 100644 --- a/examples/ox/src/language/main.ts +++ b/examples/ox/src/language/main.ts @@ -4,10 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { startLanguageServer } from 'langium/lsp'; -import { NodeFileSystem } from 'langium/node'; -import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js'; -import { createOxServices } from './ox-module.js'; +import { startLanguageServer } from "langium/lsp"; +import { NodeFileSystem } from "langium/node"; +import { + createConnection, + ProposedFeatures, +} from "vscode-languageserver/node.js"; +import { createOxServices } from "./ox-module.js"; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); diff --git a/examples/ox/src/language/ox-module.ts b/examples/ox/src/language/ox-module.ts index 1516714b..07905f0e 100644 --- a/examples/ox/src/language/ox-module.ts +++ b/examples/ox/src/language/ox-module.ts @@ -4,42 +4,61 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { LangiumSharedCoreServices, Module, inject } from 'langium'; -import { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, PartialLangiumServices, createDefaultModule, createDefaultSharedModule } from 'langium/lsp'; -import { TypirLangiumServices, createTypirLangiumServices, initializeLangiumTypirServices } from 'typir-langium'; -import { OxAstType, reflection } from './generated/ast.js'; -import { OxGeneratedModule, OxGeneratedSharedModule } from './generated/module.js'; -import { OxTypeSystem } from './ox-type-checking.js'; -import { OxValidator, registerValidationChecks } from './ox-validator.js'; +import { LangiumSharedCoreServices, Module, inject } from "langium"; +import { + DefaultSharedModuleContext, + LangiumServices, + LangiumSharedServices, + PartialLangiumServices, + createDefaultModule, + createDefaultSharedModule, +} from "langium/lsp"; +import { + TypirLangiumServices, + createTypirLangiumServices, + initializeLangiumTypirServices, +} from "typir-langium"; +import { OxAstType, reflection } from "./generated/ast.js"; +import { + OxGeneratedModule, + OxGeneratedSharedModule, +} from "./generated/module.js"; +import { OxTypeSystem } from "./ox-type-checking.js"; +import { OxValidator, registerValidationChecks } from "./ox-validator.js"; /** * Declaration of custom services - add your own service classes here. */ export type OxAddedServices = { validation: { - OxValidator: OxValidator - }, - typir: TypirLangiumServices, // all Langium services are able to access these Typir services for type-checking -} + OxValidator: OxValidator; + }; + typir: TypirLangiumServices; // all Langium services are able to access these Typir services for type-checking +}; /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ -export type OxServices = LangiumServices & OxAddedServices +export type OxServices = LangiumServices & OxAddedServices; /** * Dependency injection module that overrides Langium default services and contributes the * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ -export function createOxModule(shared: LangiumSharedCoreServices): Module { +export function createOxModule( + shared: LangiumSharedCoreServices, +): Module { return { validation: { - OxValidator: () => new OxValidator() + OxValidator: () => new OxValidator(), }, // For type checking with Typir, configure the Typir & Typir-Langium services in this way: - typir: () => createTypirLangiumServices(shared, reflection, new OxTypeSystem(), { /* customize Typir services here */ }), + typir: () => + createTypirLangiumServices(shared, reflection, new OxTypeSystem(), { + /* customize Typir services here */ + }), }; } @@ -59,12 +78,12 @@ export function createOxModule(shared: LangiumSharedCoreServices): Module { - onInitialize(typir: TypirLangiumServices): void { // define primitive types // typeBool, typeNumber and typeVoid are specific types for OX, ... - const typeBool = typir.factory.Primitives.create({ primitiveName: 'boolean' }) + const typeBool = typir.factory.Primitives.create({ + primitiveName: "boolean", + }) .inferenceRule({ filter: isBooleanLiteral }) - .inferenceRule({ filter: isTypeReference, matching: node => node.primitive === 'boolean' }) + .inferenceRule({ + filter: isTypeReference, + matching: (node) => node.primitive === "boolean", + }) .finish(); // ... but their primitive kind is provided/preset by Typir - const typeNumber = typir.factory.Primitives.create({ primitiveName: 'number' }) + const typeNumber = typir.factory.Primitives.create({ + primitiveName: "number", + }) .inferenceRule({ languageKey: NumberLiteral }) - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'number' }) + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "number", + }) .finish(); - const typeVoid = typir.factory.Primitives.create({ primitiveName: 'void' }) - .inferenceRule({ languageKey: TypeReference, matching: (node: TypeReference) => node.primitive === 'void' }) + const typeVoid = typir.factory.Primitives.create({ + primitiveName: "void", + }) + .inferenceRule({ + languageKey: TypeReference, + matching: (node: TypeReference) => node.primitive === "void", + }) .finish(); // extract inference rules, which is possible here thanks to the unified structure of the Langium grammar (but this is not possible in general!) - const binaryInferenceRule: InferOperatorWithMultipleOperands = { + const binaryInferenceRule: InferOperatorWithMultipleOperands< + AstNode, + BinaryExpression + > = { filter: isBinaryExpression, - matching: (node: BinaryExpression, name: string) => node.operator === name, - operands: (node: BinaryExpression, _name: string) => [node.left, node.right], + matching: (node: BinaryExpression, name: string) => + node.operator === name, + operands: (node: BinaryExpression, _name: string) => [ + node.left, + node.right, + ], validateArgumentsOfCalls: true, }; - const unaryInferenceRule: InferOperatorWithSingleOperand = { + const unaryInferenceRule: InferOperatorWithSingleOperand< + AstNode, + UnaryExpression + > = { filter: isUnaryExpression, - matching: (node: UnaryExpression, name: string) => node.operator === name, + matching: (node: UnaryExpression, name: string) => + node.operator === name, operand: (node: UnaryExpression, _name: string) => node.value, validateArgumentsOfCalls: true, }; // define operators // binary operators: numbers => number - for (const operator of ['+', '-', '*', '/']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeNumber, right: typeNumber, return: typeNumber }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["+", "-", "*", "/"]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeNumber, + right: typeNumber, + return: typeNumber, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // binary operators: numbers => boolean - for (const operator of ['<', '<=', '>', '>=']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeNumber, right: typeNumber, return: typeBool }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["<", "<=", ">", ">="]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeNumber, + right: typeNumber, + return: typeBool, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // binary operators: booleans => boolean - for (const operator of ['and', 'or']) { - typir.factory.Operators.createBinary({ name: operator, signature: { left: typeBool, right: typeBool, return: typeBool }}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["and", "or"]) { + typir.factory.Operators.createBinary({ + name: operator, + signature: { + left: typeBool, + right: typeBool, + return: typeBool, + }, + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // ==, != for booleans and numbers - for (const operator of ['==', '!=']) { - typir.factory.Operators.createBinary({ name: operator, signatures: [ - { left: typeNumber, right: typeNumber, return: typeBool }, - { left: typeBool, right: typeBool, return: typeBool }, - ]}).inferenceRule(binaryInferenceRule).finish(); + for (const operator of ["==", "!="]) { + typir.factory.Operators.createBinary({ + name: operator, + signatures: [ + { left: typeNumber, right: typeNumber, return: typeBool }, + { left: typeBool, right: typeBool, return: typeBool }, + ], + }) + .inferenceRule(binaryInferenceRule) + .finish(); } // unary operators - typir.factory.Operators.createUnary({ name: '!', signature: { operand: typeBool, return: typeBool }}).inferenceRule(unaryInferenceRule).finish(); - typir.factory.Operators.createUnary({ name: '-', signature: { operand: typeNumber, return: typeNumber }}).inferenceRule(unaryInferenceRule).finish(); + typir.factory.Operators.createUnary({ + name: "!", + signature: { operand: typeBool, return: typeBool }, + }) + .inferenceRule(unaryInferenceRule) + .finish(); + typir.factory.Operators.createUnary({ + name: "-", + signature: { operand: typeNumber, return: typeNumber }, + }) + .inferenceRule(unaryInferenceRule) + .finish(); /** Hints regarding the order of Typir configurations for OX: * - In general, Typir aims to not depend on the order of configurations. @@ -115,38 +211,85 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { typir.validation.Collector.addValidationRulesForAstNodes({ AssignmentStatement: (node, accept, typir) => { if (node.varRef.ref) { - typir.validation.Constraints.ensureNodeIsAssignable(node.value, node.varRef.ref, accept, + typir.validation.Constraints.ensureNodeIsAssignable( + node.value, + node.varRef.ref, + accept, (actual, expected) => ({ message: `The expression '${node.value.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.varRef.ref!.name}' with type '${expected.name}'.`, - languageProperty: 'value', - })); + languageProperty: "value", + }), + ); } }, ForStatement: validateCondition, IfStatement: validateCondition, ReturnStatement: (node, accept, typir) => { - const functionDeclaration = AstUtils.getContainerOfType(node, isFunctionDeclaration); - if (functionDeclaration && functionDeclaration.returnType.primitive !== 'void' && node.value) { + const functionDeclaration = AstUtils.getContainerOfType( + node, + isFunctionDeclaration, + ); + if ( + functionDeclaration && + functionDeclaration.returnType.primitive !== "void" && + node.value + ) { // the return value must fit to the return type of the function - typir.validation.Constraints.ensureNodeIsAssignable(node.value, functionDeclaration.returnType, accept, - () => ({ message: `The expression '${node.value!.$cstNode?.text}' is not usable as return value for the function '${functionDeclaration.name}'.`, languageProperty: 'value' })); + typir.validation.Constraints.ensureNodeIsAssignable( + node.value, + functionDeclaration.returnType, + accept, + () => ({ + message: `The expression '${node.value!.$cstNode?.text}' is not usable as return value for the function '${functionDeclaration.name}'.`, + languageProperty: "value", + }), + ); } }, VariableDeclaration: (node, accept, typir) => { - typir.validation.Constraints.ensureNodeHasNotType(node, typeVoid, accept, - () => ({ message: "Variables can't be declared with the type 'void'.", languageProperty: 'type' })); - typir.validation.Constraints.ensureNodeIsAssignable(node.value, node, accept, - (actual, expected) => ({ message: `The initialization expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.name}' with type '${expected.name}'.`, languageProperty: 'value' })); + typir.validation.Constraints.ensureNodeHasNotType( + node, + typeVoid, + accept, + () => ({ + message: + "Variables can't be declared with the type 'void'.", + languageProperty: "type", + }), + ); + typir.validation.Constraints.ensureNodeIsAssignable( + node.value, + node, + accept, + (actual, expected) => ({ + message: `The initialization expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.name}' with type '${expected.name}'.`, + languageProperty: "value", + }), + ); }, WhileStatement: validateCondition, }); - function validateCondition(node: IfStatement | WhileStatement | ForStatement, accept: ValidationProblemAcceptor, typir: TypirServices): void { - typir.validation.Constraints.ensureNodeIsAssignable(node.condition, typeBool, accept, - () => ({ message: "Conditions need to be evaluated to 'boolean'.", languageProperty: 'condition' })); + function validateCondition( + node: IfStatement | WhileStatement | ForStatement, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ): void { + typir.validation.Constraints.ensureNodeIsAssignable( + node.condition, + typeBool, + accept, + () => ({ + message: "Conditions need to be evaluated to 'boolean'.", + languageProperty: "condition", + }), + ); } } - onNewAstNode(languageNode: AstNode, typir: TypirLangiumServices): void { + onNewAstNode( + languageNode: AstNode, + typir: TypirLangiumServices, + ): void { // define function types // they have to be updated after each change of the Langium document, since they are derived from the user-defined FunctionDeclarations! if (isFunctionDeclaration(languageNode)) { @@ -155,14 +298,24 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { typir.factory.Functions.create({ functionName, // note that the following two lines internally use type inference here in order to map language types to Typir types - outputParameter: { name: NO_PARAMETER_NAME, type: languageNode.returnType }, - inputParameters: languageNode.parameters.map(p => (>{ name: p.name, type: p.type })), + outputParameter: { + name: NO_PARAMETER_NAME, + type: languageNode.returnType, + }, + inputParameters: languageNode.parameters.map( + (p) => + >{ + name: p.name, + type: p.type, + }, + ), associatedLanguageNode: languageNode, }) // inference rule for function declaration: .inferenceRuleForDeclaration({ languageKey: FunctionDeclaration, - matching: (node: FunctionDeclaration) => node === languageNode // only the current function declaration matches! + matching: (node: FunctionDeclaration) => + node === languageNode, // only the current function declaration matches! }) /** inference rule for funtion calls: * - inferring of overloaded functions works only, if the actual arguments have the expected types! @@ -170,7 +323,10 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { * - additionally, validations for the assigned values to the expected parameter( type)s are derived */ .inferenceRuleForCalls({ languageKey: MemberCall, - matching: (call: MemberCall) => isFunctionDeclaration(call.element.ref) && call.explicitOperationCall && call.element.ref.name === functionName, + matching: (call: MemberCall) => + isFunctionDeclaration(call.element.ref) && + call.explicitOperationCall && + call.element.ref.name === functionName, inputArguments: (call: MemberCall) => call.arguments, // they are needed to check, that the given arguments are assignable to the parameters // Note that OX does not support overloaded function declarations for simplicity: Look into LOX to see how to handle overloaded functions and methods! validateArgumentsOfFunctionCalls: true, @@ -178,5 +334,4 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { .finish(); } } - } diff --git a/examples/ox/src/language/ox-validator.ts b/examples/ox/src/language/ox-validator.ts index d935a79e..0433af56 100644 --- a/examples/ox/src/language/ox-validator.ts +++ b/examples/ox/src/language/ox-validator.ts @@ -4,9 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstUtils, MultiMap, type ValidationAcceptor, type ValidationChecks } from 'langium'; -import { FunctionDeclaration, isFunctionDeclaration, isVariableDeclaration, OxElement, OxProgram, VariableDeclaration, type OxAstType, type ReturnStatement } from './generated/ast.js'; -import type { OxServices } from './ox-module.js'; +import { + AstUtils, + MultiMap, + type ValidationAcceptor, + type ValidationChecks, +} from "langium"; +import { + FunctionDeclaration, + isFunctionDeclaration, + isVariableDeclaration, + OxElement, + OxProgram, + VariableDeclaration, + type OxAstType, + type ReturnStatement, +} from "./generated/ast.js"; +import type { OxServices } from "./ox-module.js"; /** * Register custom validation checks. @@ -30,14 +44,23 @@ export function registerValidationChecks(services: OxServices) { * Validations on type level are done by Typir. */ export class OxValidator { - - checkReturnTypeIsCorrect(node: ReturnStatement, accept: ValidationAcceptor) { - const functionDeclaration = AstUtils.getContainerOfType(node, isFunctionDeclaration); + checkReturnTypeIsCorrect( + node: ReturnStatement, + accept: ValidationAcceptor, + ) { + const functionDeclaration = AstUtils.getContainerOfType( + node, + isFunctionDeclaration, + ); if (functionDeclaration) { - if (functionDeclaration.returnType.primitive === 'void') { + if (functionDeclaration.returnType.primitive === "void") { // no return type if (node.value) { - accept('error', `The function '${functionDeclaration.name}' has 'void' as return type. Therefore, this return statement must return no value.`, { node, property: 'value' }); + accept( + "error", + `The function '${functionDeclaration.name}' has 'void' as return type. Therefore, this return statement must return no value.`, + { node, property: "value" }, + ); } else { // no value => everything is fine } @@ -47,13 +70,20 @@ export class OxValidator { // the validation that return value fits to return type is done by Typir, not here } else { // missing return value - accept('error', `The function '${functionDeclaration.name}' has '${functionDeclaration.returnType.primitive}' as return type. Therefore, this return statement must return value.`, { node }); + accept( + "error", + `The function '${functionDeclaration.name}' has '${functionDeclaration.returnType.primitive}' as return type. Therefore, this return statement must return value.`, + { node }, + ); } } } } - checkUniqueVariableNames(block: { elements: OxElement[]}, accept: ValidationAcceptor): void { + checkUniqueVariableNames( + block: { elements: OxElement[] }, + accept: ValidationAcceptor, + ): void { const variables: Map = new Map(); for (const v of block.elements) { if (isVariableDeclaration(v)) { @@ -69,28 +99,44 @@ export class OxValidator { for (const [name, vars] of variables.entries()) { if (vars.length >= 2) { for (const v of vars) { - accept('error', 'Variables need to have unique names: ' + name, { - node: v, - property: 'name' - }); + accept( + "error", + "Variables need to have unique names: " + name, + { + node: v, + property: "name", + }, + ); } } } } - checkUniqueFunctionNames(root: OxProgram, accept: ValidationAcceptor): void { - const mappedFunctions: MultiMap = new MultiMap(); - root.elements.filter(isFunctionDeclaration).forEach(decl => mappedFunctions.add(decl.name, decl)); - for (const [name, declarations] of mappedFunctions.entriesGroupedByKey()) { + checkUniqueFunctionNames( + root: OxProgram, + accept: ValidationAcceptor, + ): void { + const mappedFunctions: MultiMap = + new MultiMap(); + root.elements + .filter(isFunctionDeclaration) + .forEach((decl) => mappedFunctions.add(decl.name, decl)); + for (const [ + name, + declarations, + ] of mappedFunctions.entriesGroupedByKey()) { if (declarations.length >= 2) { for (const f of declarations) { - accept('error', 'Functions need to have unique names: ' + name, { - node: f, - property: 'name' - }); + accept( + "error", + "Functions need to have unique names: " + name, + { + node: f, + property: "name", + }, + ); } } } } - } diff --git a/examples/ox/test/ox-type-checking-functions.test.ts b/examples/ox/test/ox-type-checking-functions.test.ts index 0f20b136..9f12a34d 100644 --- a/examples/ox/test/ox-type-checking-functions.test.ts +++ b/examples/ox/test/ox-type-checking-functions.test.ts @@ -4,32 +4,36 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { validateOx } from './ox-type-checking-utils.js'; +import { describe, test } from "vitest"; +import { validateOx } from "./ox-type-checking-utils.js"; -describe('Test type checking for statements and variables in OX', () => { - - test('function: return value and return type', async () => { - await validateOx('fun myFunction1() : boolean { return true; }', 0); - await validateOx('fun myFunction2() : boolean { return 2; }', 1); - await validateOx('fun myFunction3() : number { return 2; }', 0); - await validateOx('fun myFunction4() : number { return true; }', 1); +describe("Test type checking for statements and variables in OX", () => { + test("function: return value and return type", async () => { + await validateOx("fun myFunction1() : boolean { return true; }", 0); + await validateOx("fun myFunction2() : boolean { return 2; }", 1); + await validateOx("fun myFunction3() : number { return 2; }", 0); + await validateOx("fun myFunction4() : number { return true; }", 1); }); - test('function: the same function name twice (in the same file) is not allowed in Typir', async () => { - await validateOx(` + test("function: the same function name twice (in the same file) is not allowed in Typir", async () => { + await validateOx( + ` fun myFunction() : boolean { return true; } fun myFunction() : boolean { return false; } - `, [ - 'Functions need to have unique names', - 'Functions need to have unique names', - ]); + `, + [ + "Functions need to have unique names", + "Functions need to have unique names", + ], + ); }); // TODO this test case needs to be investigated in more detail - test.todo('function: the same function name twice (even in different files) is not allowed in Typir', async () => { - await validateOx('fun myFunction() : boolean { return true; }', 0); - await validateOx('fun myFunction() : boolean { return false; }', 2); // now, both functions should be marked as "duplicate" - }); - + test.todo( + "function: the same function name twice (even in different files) is not allowed in Typir", + async () => { + await validateOx("fun myFunction() : boolean { return true; }", 0); + await validateOx("fun myFunction() : boolean { return false; }", 2); // now, both functions should be marked as "duplicate" + }, + ); }); diff --git a/examples/ox/test/ox-type-checking-operators.test.ts b/examples/ox/test/ox-type-checking-operators.test.ts index 19bb3dfe..51a1c8fc 100644 --- a/examples/ox/test/ox-type-checking-operators.test.ts +++ b/examples/ox/test/ox-type-checking-operators.test.ts @@ -4,66 +4,64 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { validateOx } from './ox-type-checking-utils.js'; +import { describe, test } from "vitest"; +import { validateOx } from "./ox-type-checking-utils.js"; -describe('Test type checking for statements and variables in OX', () => { +describe("Test type checking for statements and variables in OX", () => { + test("binary operators", async () => { + await validateOx("var myResult: number = 2 + 3;", 0); + await validateOx("var myResult: number = 2 - 3;", 0); + await validateOx("var myResult: number = 2 * 3;", 0); + await validateOx("var myResult: number = 2 / 3;", 0); - test('binary operators', async () => { - await validateOx('var myResult: number = 2 + 3;', 0); - await validateOx('var myResult: number = 2 - 3;', 0); - await validateOx('var myResult: number = 2 * 3;', 0); - await validateOx('var myResult: number = 2 / 3;', 0); + await validateOx("var myResult: boolean = 2 < 3;", 0); + await validateOx("var myResult: boolean = 2 <= 3;", 0); + await validateOx("var myResult: boolean = 2 > 3;", 0); + await validateOx("var myResult: boolean = 2 >= 3;", 0); - await validateOx('var myResult: boolean = 2 < 3;', 0); - await validateOx('var myResult: boolean = 2 <= 3;', 0); - await validateOx('var myResult: boolean = 2 > 3;', 0); - await validateOx('var myResult: boolean = 2 >= 3;', 0); + await validateOx("var myResult: boolean = true and false;", 0); + await validateOx("var myResult: boolean = true or false;", 0); - await validateOx('var myResult: boolean = true and false;', 0); - await validateOx('var myResult: boolean = true or false;', 0); + await validateOx("var myResult: boolean = 2 == 3;", 0); + await validateOx("var myResult: boolean = 2 != 3;", 0); + await validateOx("var myResult: boolean = true == false;", 0); + await validateOx("var myResult: boolean = true != false;", 0); - await validateOx('var myResult: boolean = 2 == 3;', 0); - await validateOx('var myResult: boolean = 2 != 3;', 0); - await validateOx('var myResult: boolean = true == false;', 0); - await validateOx('var myResult: boolean = true != false;', 0); - - await validateOx('var myResult: boolean = true == 3;', 1); - await validateOx('var myResult: boolean = 2 != false;', 1); + await validateOx("var myResult: boolean = true == 3;", 1); + await validateOx("var myResult: boolean = 2 != false;", 1); }); - test('unary operator: !', async () => { - await validateOx('var myResult: boolean = !true;', 0); - await validateOx('var myResult: boolean = !!true;', 0); - await validateOx('var myResult: boolean = !!!true;', 0); + test("unary operator: !", async () => { + await validateOx("var myResult: boolean = !true;", 0); + await validateOx("var myResult: boolean = !!true;", 0); + await validateOx("var myResult: boolean = !!!true;", 0); }); - test('unary operator: -', async () => { - await validateOx('var myResult: number = -2;', 0); - await validateOx('var myResult: number = --2;', 0); - await validateOx('var myResult: number = ---2;', 0); + test("unary operator: -", async () => { + await validateOx("var myResult: number = -2;", 0); + await validateOx("var myResult: number = --2;", 0); + await validateOx("var myResult: number = ---2;", 0); }); - test('use overloaded operators', async () => { - await validateOx('var myVar : boolean = true == false;', 0); - await validateOx('var myVar : boolean = 2 == 3;', 0); - await validateOx('var myVar : boolean = true == 3;', 1); - await validateOx('var myVar : boolean = 2 == false;', 1); + test("use overloaded operators", async () => { + await validateOx("var myVar : boolean = true == false;", 0); + await validateOx("var myVar : boolean = 2 == 3;", 0); + await validateOx("var myVar : boolean = true == 3;", 1); + await validateOx("var myVar : boolean = 2 == false;", 1); }); test('Only a single problem with the inner expression, since the type of "*" is always number!', async () => { - await validateOx('var myVar : number = 2 * (2 * false);', [ + await validateOx("var myVar : number = 2 * (2 * false);", [ "While validating the AstNode '(2 * false)', this error is found: The given operands for the call of '*' don't match.", ]); }); test('Two issues in nested expressions, since "*" expects always numbers, while "and" returns always booleans!', async () => { - await validateOx('var myVar : number = 2 * (2 and false);', [ + await validateOx("var myVar : number = 2 * (2 and false);", [ // this is obvious: left and right need to have the same type "While validating the AstNode '(2 and false)', this error is found: The given operands for the call of 'and' don't match.", // '*' supports only numbers for left and right, but the right operand is always boolean as result of the 'and' operator "While validating the AstNode '2 * (2 and false)', this error is found: The given operands for the call of '*' don't match.", ]); }); - }); diff --git a/examples/ox/test/ox-type-checking-statements.test.ts b/examples/ox/test/ox-type-checking-statements.test.ts index 1b0fa4e8..6e202ffa 100644 --- a/examples/ox/test/ox-type-checking-statements.test.ts +++ b/examples/ox/test/ox-type-checking-statements.test.ts @@ -4,46 +4,44 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { validateOx } from './ox-type-checking-utils.js'; +import { describe, test } from "vitest"; +import { validateOx } from "./ox-type-checking-utils.js"; -describe('Test type checking for statements and variables in OX', () => { - - test('multiple nested and', async () => { - await validateOx('var myResult: boolean = true and false;', 0); - await validateOx('var myResult: boolean = true and false and true;', 0); +describe("Test type checking for statements and variables in OX", () => { + test("multiple nested and", async () => { + await validateOx("var myResult: boolean = true and false;", 0); + await validateOx("var myResult: boolean = true and false and true;", 0); }); - test('number assignments', async () => { - await validateOx('var myResult: number = 2;', 0); - await validateOx('var myResult: number = 2 * 3;', 0); - await validateOx('var myResult: number = 2 < 3;', 1); - await validateOx('var myResult: number = true;', 1); + test("number assignments", async () => { + await validateOx("var myResult: number = 2;", 0); + await validateOx("var myResult: number = 2 * 3;", 0); + await validateOx("var myResult: number = 2 < 3;", 1); + await validateOx("var myResult: number = true;", 1); }); - test('boolean assignments', async () => { - await validateOx('var myResult: boolean = true;', 0); - await validateOx('var myResult: boolean = 2;', 1); - await validateOx('var myResult: boolean = 2 * 3;', 1); - await validateOx('var myResult: boolean = 2 < 3;', 0); + test("boolean assignments", async () => { + await validateOx("var myResult: boolean = true;", 0); + await validateOx("var myResult: boolean = 2;", 1); + await validateOx("var myResult: boolean = 2 * 3;", 1); + await validateOx("var myResult: boolean = 2 < 3;", 0); }); - test('statement assignments', async () => { - await validateOx('var myResult: boolean; myResult = true;', 0); - await validateOx('var myResult: boolean; myResult = 2;', 1); - await validateOx('var myResult: boolean; myResult = 2 * 3;', 1); - await validateOx('var myResult: boolean; myResult = 2 < 3;', 0); + test("statement assignments", async () => { + await validateOx("var myResult: boolean; myResult = true;", 0); + await validateOx("var myResult: boolean; myResult = 2;", 1); + await validateOx("var myResult: boolean; myResult = 2 * 3;", 1); + await validateOx("var myResult: boolean; myResult = 2 < 3;", 0); }); - test('boolean in conditions', async () => { - await validateOx('if ( true ) {}', 0); - await validateOx('if ( 3 ) {}', 1); + test("boolean in conditions", async () => { + await validateOx("if ( true ) {}", 0); + await validateOx("if ( 3 ) {}", 1); }); - test('variable declarations', async () => { - await validateOx('var myVar : boolean;', 0); - await validateOx('var myVar : number;', 0); - await validateOx('var myVar : void;', 1); + test("variable declarations", async () => { + await validateOx("var myVar : boolean;", 0); + await validateOx("var myVar : number;", 0); + await validateOx("var myVar : void;", 1); }); - }); diff --git a/examples/ox/test/ox-type-checking-utils.ts b/examples/ox/test/ox-type-checking-utils.ts index 5f8c415f..b251bff3 100644 --- a/examples/ox/test/ox-type-checking-utils.ts +++ b/examples/ox/test/ox-type-checking-utils.ts @@ -4,16 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EmptyFileSystem } from 'langium'; -import { parseDocument } from 'langium/test'; -import { isFunctionType } from 'typir'; -import { deleteAllDocuments } from 'typir-langium'; -import { compareValidationIssuesStrict, expectTypirTypes } from 'typir/test'; -import { afterEach, expect } from 'vitest'; -import { createOxServices } from '../src/language/ox-module.js'; +import { EmptyFileSystem } from "langium"; +import { parseDocument } from "langium/test"; +import { isFunctionType } from "typir"; +import { deleteAllDocuments } from "typir-langium"; +import { compareValidationIssuesStrict, expectTypirTypes } from "typir/test"; +import { afterEach, expect } from "vitest"; +import { createOxServices } from "../src/language/ox-module.js"; export const oxServices = createOxServices(EmptyFileSystem).Ox; -export const operatorNames = ['-', '*', '/', '+', '<', '<=', '>', '>=', 'and', 'or', '==', '==', '!=', '!=', '!', '-']; +export const operatorNames = [ + "-", + "*", + "/", + "+", + "<", + "<=", + ">", + ">=", + "and", + "or", + "==", + "==", + "!=", + "!=", + "!", + "-", +]; afterEach(async () => { await deleteAllDocuments(oxServices.shared); @@ -21,13 +38,18 @@ afterEach(async () => { expectTypirTypes(oxServices.typir, isFunctionType, ...operatorNames); }); -export async function validateOx(ox: string, errors: number | string | string[]) { +export async function validateOx( + ox: string, + errors: number | string | string[], +) { const document = await parseDocument(oxServices, ox.trim()); - const diagnostics: string[] = (await oxServices.validation.DocumentValidator.validateDocument(document)).map(d => d.message); - const msgError = diagnostics.join('\n'); - if (typeof errors === 'number') { + const diagnostics: string[] = ( + await oxServices.validation.DocumentValidator.validateDocument(document) + ).map((d) => d.message); + const msgError = diagnostics.join("\n"); + if (typeof errors === "number") { expect(diagnostics, msgError).toHaveLength(errors); - } else if (typeof errors === 'string') { + } else if (typeof errors === "string") { expect(diagnostics, msgError).toHaveLength(1); expect(diagnostics[0], msgError).includes(errors); } else { diff --git a/package-lock.json b/package-lock.json index 2e551ae5..57456f1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,9 +22,10 @@ "concurrently": "~9.0.1", "editorconfig": "~2.0.0", "esbuild": "^0.25.0", - "eslint": "~8.57.1", + "eslint": "^9.27.0", "eslint-plugin-header": "~3.1.1", "fs-extra": "^11.2.0", + "prettier": "^3.5.3", "semver": "^7.7.1", "shx": "~0.3.4", "typescript": "~5.8.2", @@ -561,36 +562,150 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", - "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -606,12 +721,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", @@ -880,6 +1002,13 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.19.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", @@ -928,17 +1057,17 @@ } } }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -956,14 +1085,17 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -971,18 +1103,22 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/type-utils": { + "node_modules/@typescript-eslint/parser": { "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1000,6 +1136,23 @@ } } }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/types": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", @@ -1065,28 +1218,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", @@ -1104,12 +1235,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/@vitest/expect": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", @@ -1239,11 +1364,11 @@ } }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, - "peer": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1256,6 +1381,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1265,6 +1391,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1300,6 +1427,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1359,6 +1493,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1597,18 +1732,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/editorconfig": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-2.0.0.tgz", @@ -1719,59 +1842,64 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-plugin-header": { @@ -1783,34 +1911,28 @@ "eslint": ">=7.7.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1818,24 +1940,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1852,48 +1956,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1906,16 +1981,35 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "argparse": "^2.0.1" + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -1930,20 +2024,12 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1951,11 +2037,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -1974,6 +2061,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -1982,7 +2070,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -2004,7 +2093,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -2028,15 +2118,16 @@ "dev": true }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -2068,17 +2159,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -2173,15 +2264,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2250,10 +2339,11 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2351,32 +2441,38 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -2410,6 +2506,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -2677,6 +2774,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -2796,11 +2894,28 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2874,6 +2989,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2888,22 +3004,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", @@ -3126,6 +3226,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -3157,12 +3258,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3301,18 +3396,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -3362,6 +3445,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index 1690a1ae..37b7934d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "clean": "shx rm -rf packages/**/lib packages/**/out packages/**/*.tsbuildinfo examples/**/lib examples/**/out examples/**/*.tsbuildinfo", "build": "tsc -b tsconfig.build.json && npm run build --workspaces", "watch": "concurrently -n typir,typir-langium,ox,lox -c blue,blue,green,green \"tsc -b tsconfig.build.json -w\" \"npm run watch --workspace=typir\" \"npm run watch --workspace=typir-langium\" \"npm run watch --workspace=examples/ox\" \"npm run watch --workspace=examples/lox\"", - "lint": "npm run lint --workspaces", + "lint": "eslint .", + "pretty": "prettier -w \"**/*.ts\"", + "pretty:check": "prettier -c \"**/*.ts\"", "test": "vitest", "test:run": "vitest --run", "test-ui": "vitest --ui", @@ -37,9 +39,10 @@ "concurrently": "~9.0.1", "editorconfig": "~2.0.0", "esbuild": "^0.25.0", - "eslint": "~8.57.1", + "eslint": "^9.27.0", "eslint-plugin-header": "~3.1.1", "fs-extra": "^11.2.0", + "prettier": "^3.5.3", "semver": "^7.7.1", "shx": "~0.3.4", "typescript": "~5.8.2", diff --git a/packages/typir-langium/src/features/langium-caching.ts b/packages/typir-langium/src/features/langium-caching.ts index f2742bdc..dfde5cd2 100644 --- a/packages/typir-langium/src/features/langium-caching.ts +++ b/packages/typir-langium/src/features/langium-caching.ts @@ -4,16 +4,26 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, DocumentCache, DocumentState, LangiumSharedCoreServices } from 'langium'; -import { CachePending, LanguageNodeInferenceCaching, Type } from 'typir'; -import { getDocumentKey } from '../utils/typir-langium-utils.js'; +import { + AstNode, + DocumentCache, + DocumentState, + LangiumSharedCoreServices, +} from "langium"; +import { CachePending, LanguageNodeInferenceCaching, Type } from "typir"; +import { getDocumentKey } from "../utils/typir-langium-utils.js"; // cache AstNodes -export class LangiumLanguageNodeInferenceCaching implements LanguageNodeInferenceCaching { +export class LangiumLanguageNodeInferenceCaching + implements LanguageNodeInferenceCaching +{ protected readonly cache: DocumentCache; // removes cached AstNodes, if their underlying LangiumDocuments are invalidated constructor(langiumServices: LangiumSharedCoreServices) { - this.cache = new DocumentCache(langiumServices, DocumentState.IndexedReferences); + this.cache = new DocumentCache( + langiumServices, + DocumentState.IndexedReferences, + ); } cacheSet(languageNode: AstNode, type: Type): void { @@ -25,7 +35,10 @@ export class LangiumLanguageNodeInferenceCaching implements LanguageNodeInferenc if (this.pendingGet(languageNode)) { return undefined; } else { - return this.cache.get(getDocumentKey(languageNode), languageNode) as (Type | undefined); + return this.cache.get( + getDocumentKey(languageNode), + languageNode, + ) as Type | undefined; } } @@ -34,7 +47,11 @@ export class LangiumLanguageNodeInferenceCaching implements LanguageNodeInferenc } pendingSet(languageNode: AstNode): void { - this.cache.set(getDocumentKey(languageNode), languageNode, CachePending); + this.cache.set( + getDocumentKey(languageNode), + languageNode, + CachePending, + ); } pendingClear(languageNode: AstNode): void { @@ -48,6 +65,9 @@ export class LangiumLanguageNodeInferenceCaching implements LanguageNodeInferenc pendingGet(languageNode: AstNode): boolean { const key = getDocumentKey(languageNode); - return this.cache.has(key, languageNode) && this.cache.get(key, languageNode) === CachePending; + return ( + this.cache.has(key, languageNode) && + this.cache.get(key, languageNode) === CachePending + ); } } diff --git a/packages/typir-langium/src/features/langium-inference.ts b/packages/typir-langium/src/features/langium-inference.ts index 878a5967..227ed60b 100644 --- a/packages/typir-langium/src/features/langium-inference.ts +++ b/packages/typir-langium/src/features/langium-inference.ts @@ -4,27 +4,48 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode } from 'langium'; -import { DefaultTypeInferenceCollector, TypeInferenceCollector, TypeInferenceRule } from 'typir'; -import { LangiumAstTypes } from '../utils/typir-langium-utils.js'; +import { AstNode } from "langium"; +import { + DefaultTypeInferenceCollector, + TypeInferenceCollector, + TypeInferenceRule, +} from "typir"; +import { LangiumAstTypes } from "../utils/typir-langium-utils.js"; export type LangiumTypeInferenceRules = { - [K in keyof T]?: T[K] extends AstNode ? TypeInferenceRule | Array> : never + [K in keyof T]?: T[K] extends AstNode + ? + | TypeInferenceRule + | Array> + : never; } & { - AstNode?: TypeInferenceRule | Array>; -} + AstNode?: + | TypeInferenceRule + | Array>; +}; -export interface LangiumTypeInferenceCollector extends TypeInferenceCollector { - addInferenceRulesForAstNodes(rules: LangiumTypeInferenceRules): void; +export interface LangiumTypeInferenceCollector + extends TypeInferenceCollector { + addInferenceRulesForAstNodes( + rules: LangiumTypeInferenceRules, + ): void; } -export class DefaultLangiumTypeInferenceCollector extends DefaultTypeInferenceCollector implements LangiumTypeInferenceCollector { - - addInferenceRulesForAstNodes(rules: LangiumTypeInferenceRules): void { +export class DefaultLangiumTypeInferenceCollector< + AstTypes extends LangiumAstTypes, + > + extends DefaultTypeInferenceCollector + implements LangiumTypeInferenceCollector +{ + addInferenceRulesForAstNodes( + rules: LangiumTypeInferenceRules, + ): void { // map this approach for registering inference rules to the key-value approach from core Typir for (const [type, ruleCallbacks] of Object.entries(rules)) { - const languageKey = type === 'AstNode' ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key - const callbacks = ruleCallbacks as TypeInferenceRule | Array>; + const languageKey = type === "AstNode" ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key + const callbacks = ruleCallbacks as + | TypeInferenceRule + | Array>; if (Array.isArray(callbacks)) { for (const callback of callbacks) { this.addInferenceRule(callback, { languageKey }); @@ -34,5 +55,4 @@ export class DefaultLangiumTypeInferenceCollector implements LanguageService { +export class LangiumLanguageService + extends DefaultLanguageService + implements LanguageService +{ protected readonly reflection: AbstractAstReflection; protected superKeys: Map | undefined = undefined; // key => all its super-keys @@ -53,5 +59,4 @@ export class LangiumLanguageService extends DefaultLanguageService impl } return this.superKeys.get(languageKey) ?? []; } - } diff --git a/packages/typir-langium/src/features/langium-printing.ts b/packages/typir-langium/src/features/langium-printing.ts index 51a3748b..35a62fe6 100644 --- a/packages/typir-langium/src/features/langium-printing.ts +++ b/packages/typir-langium/src/features/langium-printing.ts @@ -4,17 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, isAstNode } from 'langium'; -import { DefaultTypeConflictPrinter } from 'typir'; +import { AstNode, isAstNode } from "langium"; +import { DefaultTypeConflictPrinter } from "typir"; export class LangiumProblemPrinter extends DefaultTypeConflictPrinter { - /** When printing a language node, i.e. an AstNode, print the text of the corresponding CstNode. */ - override printLanguageNode(languageNode: AstNode, sentenceBegin?: boolean | undefined): string { + override printLanguageNode( + languageNode: AstNode, + sentenceBegin?: boolean | undefined, + ): string { if (isAstNode(languageNode)) { - return `${sentenceBegin ? 'T' : 't'}he AstNode '${languageNode.$cstNode?.text}'`; + return `${sentenceBegin ? "T" : "t"}he AstNode '${languageNode.$cstNode?.text}'`; } return super.printLanguageNode(languageNode, sentenceBegin); } - } diff --git a/packages/typir-langium/src/features/langium-type-creator.ts b/packages/typir-langium/src/features/langium-type-creator.ts index 737c4d9b..7da398de 100644 --- a/packages/typir-langium/src/features/langium-type-creator.ts +++ b/packages/typir-langium/src/features/langium-type-creator.ts @@ -4,10 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, DocumentState, interruptAndCheck, LangiumDocument, LangiumSharedCoreServices } from 'langium'; -import { Type, TypeGraph, TypeGraphListener } from 'typir'; -import { TypirLangiumServices } from '../typir-langium.js'; -import { getDocumentKeyForDocument, getDocumentKeyForURI, LangiumAstTypes } from '../utils/typir-langium-utils.js'; +import { + AstNode, + AstUtils, + DocumentState, + interruptAndCheck, + LangiumDocument, + LangiumSharedCoreServices, +} from "langium"; +import { Type, TypeGraph, TypeGraphListener } from "typir"; +import { TypirLangiumServices } from "../typir-langium.js"; +import { + getDocumentKeyForDocument, + getDocumentKeyForURI, + LangiumAstTypes, +} from "../utils/typir-langium-utils.js"; /** * This service provides the API to define the actual types, inference rules and validation rules @@ -28,10 +39,12 @@ export interface LangiumTypeSystemDefinition { * @param languageNode an AstNode of the current AST * @param typir the current Typir services */ - onNewAstNode(languageNode: AstNode, typir: TypirLangiumServices): void; + onNewAstNode( + languageNode: AstNode, + typir: TypirLangiumServices, + ): void; } - /** * This service provides the API to define the actual types, inference rules and validation rules * for a textual DSL developed with Langium in order to include them into the Langium lifecycle. @@ -44,37 +57,47 @@ export interface LangiumTypeCreator { triggerInitialization(): void; } -export class DefaultLangiumTypeCreator implements LangiumTypeCreator, TypeGraphListener { +export class DefaultLangiumTypeCreator + implements LangiumTypeCreator, TypeGraphListener +{ protected initialized: boolean = false; - protected currentDocumentKey: string = ''; + protected currentDocumentKey: string = ""; protected readonly documentTypesMap: Map = new Map(); protected readonly typir: TypirLangiumServices; protected readonly typeGraph: TypeGraph; protected readonly typeSystemDefinition: LangiumTypeSystemDefinition; - constructor(typirServices: TypirLangiumServices, langiumServices: LangiumSharedCoreServices) { + constructor( + typirServices: TypirLangiumServices, + langiumServices: LangiumSharedCoreServices, + ) { this.typir = typirServices; this.typeGraph = typirServices.infrastructure.Graph; this.typeSystemDefinition = typirServices.langium.TypeSystemDefinition; // for new and updated documents: // Create Typir types after completing the Langium 'ComputedScopes' phase, since they need to be available for the following Linking phase - langiumServices.workspace.DocumentBuilder.onBuildPhase(DocumentState.ComputedScopes, async (documents, cancelToken) => { - for (const document of documents) { - await interruptAndCheck(cancelToken); - - // notify Typir about each contained node of the processed document - this.handleProcessedDocument(document); // takes care about the invalid AstNodes as well - } - }); + langiumServices.workspace.DocumentBuilder.onBuildPhase( + DocumentState.ComputedScopes, + async (documents, cancelToken) => { + for (const document of documents) { + await interruptAndCheck(cancelToken); + + // notify Typir about each contained node of the processed document + this.handleProcessedDocument(document); // takes care about the invalid AstNodes as well + } + }, + ); // for deleted documents: // Delete Typir types which are derived from AstNodes of deleted documents - langiumServices.workspace.DocumentBuilder.onUpdate((_changed, deleted) => { - deleted - .map(del => getDocumentKeyForURI(del)) - .forEach(del => this.invalidateTypesOfDocument(del)); - }); + langiumServices.workspace.DocumentBuilder.onUpdate( + (_changed, deleted) => { + deleted + .map((del) => getDocumentKeyForURI(del)) + .forEach((del) => this.invalidateTypesOfDocument(del)); + }, + ); // get informed about added/removed types in Typir's type graph this.typeGraph.addListener(this); @@ -101,19 +124,23 @@ export class DefaultLangiumTypeCreator impleme this.invalidateTypesOfDocument(this.currentDocumentKey); // create all types for this document - AstUtils.streamAst(document.parseResult.value) - .forEach((node: AstNode) => this.typeSystemDefinition.onNewAstNode(node, this.typir)); + AstUtils.streamAst(document.parseResult.value).forEach( + (node: AstNode) => + this.typeSystemDefinition.onNewAstNode(node, this.typir), + ); - this.currentDocumentKey = ''; // reset the key, newly created types will be associated with no document now + this.currentDocumentKey = ""; // reset the key, newly created types will be associated with no document now } protected invalidateTypesOfDocument(documentKey: string): void { // grab all types which were created for the document - (this.documentTypesMap.get(documentKey) + ( + this.documentTypesMap.get(documentKey) ?? // there are no types, if the document is new or if no types were created for the previous document version - ?? []) + [] + ) // this is the central way to remove types from the type systems, there is no need to inform the kinds - .forEach(typeToRemove => this.typeGraph.removeNode(typeToRemove)); + .forEach((typeToRemove) => this.typeGraph.removeNode(typeToRemove)); // remove the deleted types from the map this.documentTypesMap.delete(documentKey); } @@ -136,5 +163,4 @@ export class DefaultLangiumTypeCreator impleme onRemovedType(_type: Type): void { // since this type creator actively removes types from the type graph itself, there is no need to react on removed types } - } diff --git a/packages/typir-langium/src/features/langium-validation.ts b/packages/typir-langium/src/features/langium-validation.ts index 80aea869..a856f441 100644 --- a/packages/typir-langium/src/features/langium-validation.ts +++ b/packages/typir-langium/src/features/langium-validation.ts @@ -4,39 +4,58 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, LangiumDefaultCoreServices, ValidationAcceptor, ValidationChecks } from 'langium'; -import { DefaultValidationCollector, TypirServices, ValidationCollector, ValidationProblem, ValidationRule } from 'typir'; -import { TypirLangiumServices } from '../typir-langium.js'; -import { LangiumAstTypes } from '../utils/typir-langium-utils.js'; - -export function registerTypirValidationChecks(langiumServices: LangiumDefaultCoreServices, typirServices: TypirLangiumServices) { +import { + AstNode, + LangiumDefaultCoreServices, + ValidationAcceptor, + ValidationChecks, +} from "langium"; +import { + DefaultValidationCollector, + TypirServices, + ValidationCollector, + ValidationProblem, + ValidationRule, +} from "typir"; +import { TypirLangiumServices } from "../typir-langium.js"; +import { LangiumAstTypes } from "../utils/typir-langium-utils.js"; + +export function registerTypirValidationChecks( + langiumServices: LangiumDefaultCoreServices, + typirServices: TypirLangiumServices, +) { const registry = langiumServices.validation.ValidationRegistry; const validator = typirServices.validation.TypeValidation; - registry.registerBeforeDocument(validator.checkTypingProblemsWithTypirBeforeDocument, validator); + registry.registerBeforeDocument( + validator.checkTypingProblemsWithTypirBeforeDocument, + validator, + ); const checks: ValidationChecks = { AstNode: validator.checkTypingProblemsWithTypir, // checking each node is not performant, improve the API, see below! }; registry.register(checks, validator); - registry.registerAfterDocument(validator.checkTypingProblemsWithTypirAfterDocument, validator); + registry.registerAfterDocument( + validator.checkTypingProblemsWithTypirAfterDocument, + validator, + ); } /* -* TODO Ideas and features for the validation with Typir for Langium -* -* What to validate: -* - Is it possible to infer a type at all? Type vs undefined -* - Does the inferred type fit to the environment? => "type checking" (expected: unknown|Type, actual: unknown|Type) -* - possible Quick-fixes ... -* - for wrong type of variable declaration -* - to add missing explicit type conversion -* - no validation of parents, when their children already have some problems/warnings -* -* Improved Validation API for Langium: -* - const ref: (kind: unknown) => kind is FunctionKind = isFunctionKind; // use this signature for Langium? -* - [{ selector: isVariableDeclaration, result: languageNode => languageNode.type }, {}] Array> -* Apply the same ideas for InferenceRules as well! -*/ - + * TODO Ideas and features for the validation with Typir for Langium + * + * What to validate: + * - Is it possible to infer a type at all? Type vs undefined + * - Does the inferred type fit to the environment? => "type checking" (expected: unknown|Type, actual: unknown|Type) + * - possible Quick-fixes ... + * - for wrong type of variable declaration + * - to add missing explicit type conversion + * - no validation of parents, when their children already have some problems/warnings + * + * Improved Validation API for Langium: + * - const ref: (kind: unknown) => kind is FunctionKind = isFunctionKind; // use this signature for Langium? + * - [{ selector: isVariableDeclaration, result: languageNode => languageNode.type }, {}] Array> + * Apply the same ideas for InferenceRules as well! + */ /** * This service is a technical adapter service, @@ -48,7 +67,10 @@ export interface LangiumTypirValidator { * @param rootNode the root node of the current document * @param accept receives the found validation issues */ - checkTypingProblemsWithTypirBeforeDocument(rootNode: AstNode, accept: ValidationAcceptor): void; + checkTypingProblemsWithTypirBeforeDocument( + rootNode: AstNode, + accept: ValidationAcceptor, + ): void; /** * Executes all checks, which are directly derived from the current Typir configuration, @@ -56,45 +78,79 @@ export interface LangiumTypirValidator { * @param node the current AST node to check regarding typing issues * @param accept receives the found validation issues */ - checkTypingProblemsWithTypir(node: AstNode, accept: ValidationAcceptor): void; + checkTypingProblemsWithTypir( + node: AstNode, + accept: ValidationAcceptor, + ): void; /** * Will be called once after finishing the validation of a LangiumDocument. * @param rootNode the root node of the current document * @param accept receives the found validation issues */ - checkTypingProblemsWithTypirAfterDocument(rootNode: AstNode, accept: ValidationAcceptor): void; + checkTypingProblemsWithTypirAfterDocument( + rootNode: AstNode, + accept: ValidationAcceptor, + ): void; } -export class DefaultLangiumTypirValidator implements LangiumTypirValidator { +export class DefaultLangiumTypirValidator + implements LangiumTypirValidator +{ protected readonly services: TypirServices; constructor(services: TypirLangiumServices) { this.services = services; } - checkTypingProblemsWithTypirBeforeDocument(rootNode: AstNode, accept: ValidationAcceptor): void { - this.report(this.services.validation.Collector.validateBefore(rootNode), rootNode, accept); + checkTypingProblemsWithTypirBeforeDocument( + rootNode: AstNode, + accept: ValidationAcceptor, + ): void { + this.report( + this.services.validation.Collector.validateBefore(rootNode), + rootNode, + accept, + ); } checkTypingProblemsWithTypir(node: AstNode, accept: ValidationAcceptor) { - this.report(this.services.validation.Collector.validate(node), node, accept); + this.report( + this.services.validation.Collector.validate(node), + node, + accept, + ); } - checkTypingProblemsWithTypirAfterDocument(rootNode: AstNode, accept: ValidationAcceptor): void { - this.report(this.services.validation.Collector.validateAfter(rootNode), rootNode, accept); + checkTypingProblemsWithTypirAfterDocument( + rootNode: AstNode, + accept: ValidationAcceptor, + ): void { + this.report( + this.services.validation.Collector.validateAfter(rootNode), + rootNode, + accept, + ); } - protected report(problems: Array>, node: AstNode, accept: ValidationAcceptor): void { + protected report( + problems: Array>, + node: AstNode, + accept: ValidationAcceptor, + ): void { // print all found problems for the given AST node for (const problem of problems) { - const message = this.services.Printer.printValidationProblem(problem); - accept(problem.severity, message, { node, property: problem.languageProperty, index: problem.languageIndex }); + const message = + this.services.Printer.printValidationProblem(problem); + accept(problem.severity, message, { + node, + property: problem.languageProperty, + index: problem.languageIndex, + }); } } } - /** * Taken and adapted from 'ValidationChecks' from 'langium'. * @@ -109,23 +165,33 @@ export class DefaultLangiumTypirValidator impl * @param T a type definition mapping language specific type names (keys) to the corresponding types (values) */ export type LangiumValidationRules = { - [K in keyof T]?: T[K] extends AstNode ? ValidationRule | Array> : never + [K in keyof T]?: T[K] extends AstNode + ? ValidationRule | Array> + : never; } & { AstNode?: ValidationRule | Array>; -} - +}; -export interface LangiumValidationCollector extends ValidationCollector { - addValidationRulesForAstNodes(rules: LangiumValidationRules): void; +export interface LangiumValidationCollector + extends ValidationCollector { + addValidationRulesForAstNodes( + rules: LangiumValidationRules, + ): void; } -export class DefaultLangiumValidationCollector extends DefaultValidationCollector implements LangiumValidationCollector { - - addValidationRulesForAstNodes(rules: LangiumValidationRules): void { +export class DefaultLangiumValidationCollector + extends DefaultValidationCollector + implements LangiumValidationCollector +{ + addValidationRulesForAstNodes( + rules: LangiumValidationRules, + ): void { // map this approach for registering validation rules to the key-value approach from core Typir for (const [type, ruleCallbacks] of Object.entries(rules)) { - const languageKey = type === 'AstNode' ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key - const callbacks = ruleCallbacks as ValidationRule | Array>; + const languageKey = type === "AstNode" ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key + const callbacks = ruleCallbacks as + | ValidationRule + | Array>; if (Array.isArray(callbacks)) { for (const callback of callbacks) { this.addValidationRule(callback, { languageKey }); @@ -135,5 +201,4 @@ export class DefaultLangiumValidationCollector } } } - } diff --git a/packages/typir-langium/src/index.ts b/packages/typir-langium/src/index.ts index 54decb8f..7b218a36 100644 --- a/packages/typir-langium/src/index.ts +++ b/packages/typir-langium/src/index.ts @@ -4,11 +4,11 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -export * from './typir-langium.js'; -export * from './features/langium-caching.js'; -export * from './features/langium-inference.js'; -export * from './features/langium-language.js'; -export * from './features/langium-printing.js'; -export * from './features/langium-type-creator.js'; -export * from './features/langium-validation.js'; -export * from './utils/typir-langium-utils.js'; +export * from "./typir-langium.js"; +export * from "./features/langium-caching.js"; +export * from "./features/langium-inference.js"; +export * from "./features/langium-language.js"; +export * from "./features/langium-printing.js"; +export * from "./features/langium-type-creator.js"; +export * from "./features/langium-validation.js"; +export * from "./utils/typir-langium-utils.js"; diff --git a/packages/typir-langium/src/typir-langium.ts b/packages/typir-langium/src/typir-langium.ts index 370611f1..e6eb0ad8 100644 --- a/packages/typir-langium/src/typir-langium.ts +++ b/packages/typir-langium/src/typir-langium.ts @@ -4,15 +4,40 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AbstractAstReflection, AstNode, LangiumDefaultCoreServices, LangiumSharedCoreServices } from 'langium'; -import { createDefaultTypirServicesModule, DeepPartial, inject, Module, PartialTypirServices, TypirServices } from 'typir'; -import { LangiumLanguageNodeInferenceCaching } from './features/langium-caching.js'; -import { DefaultLangiumTypeInferenceCollector, LangiumTypeInferenceCollector } from './features/langium-inference.js'; -import { LangiumLanguageService } from './features/langium-language.js'; -import { LangiumProblemPrinter } from './features/langium-printing.js'; -import { DefaultLangiumTypeCreator, LangiumTypeCreator, LangiumTypeSystemDefinition } from './features/langium-type-creator.js'; -import { DefaultLangiumTypirValidator, DefaultLangiumValidationCollector, LangiumTypirValidator, LangiumValidationCollector, registerTypirValidationChecks } from './features/langium-validation.js'; -import { LangiumAstTypes } from './utils/typir-langium-utils.js'; +import { + AbstractAstReflection, + AstNode, + LangiumDefaultCoreServices, + LangiumSharedCoreServices, +} from "langium"; +import { + createDefaultTypirServicesModule, + DeepPartial, + inject, + Module, + PartialTypirServices, + TypirServices, +} from "typir"; +import { LangiumLanguageNodeInferenceCaching } from "./features/langium-caching.js"; +import { + DefaultLangiumTypeInferenceCollector, + LangiumTypeInferenceCollector, +} from "./features/langium-inference.js"; +import { LangiumLanguageService } from "./features/langium-language.js"; +import { LangiumProblemPrinter } from "./features/langium-printing.js"; +import { + DefaultLangiumTypeCreator, + LangiumTypeCreator, + LangiumTypeSystemDefinition, +} from "./features/langium-type-creator.js"; +import { + DefaultLangiumTypirValidator, + DefaultLangiumValidationCollector, + LangiumTypirValidator, + LangiumValidationCollector, + registerTypirValidationChecks, +} from "./features/langium-validation.js"; +import { LangiumAstTypes } from "./utils/typir-langium-utils.js"; /** * Additional Typir-Langium services to manage the Typir services @@ -20,7 +45,8 @@ import { LangiumAstTypes } from './utils/typir-langium-utils.js'; */ export type TypirLangiumAddedServices = { readonly Inference: LangiumTypeInferenceCollector; // concretizes the TypeInferenceCollector for Langium - readonly langium: { // new services which are specific for Langium + readonly langium: { + // new services which are specific for Langium readonly TypeCreator: LangiumTypeCreator; readonly TypeSystemDefinition: LangiumTypeSystemDefinition; }; @@ -28,23 +54,32 @@ export type TypirLangiumAddedServices = { readonly Collector: LangiumValidationCollector; // concretizes the ValidationCollector for Langium readonly TypeValidation: LangiumTypirValidator; // new service to integrate the validations into the Langium infrastructure }; -} +}; -export type TypirLangiumServices = TypirServices & TypirLangiumAddedServices +export type TypirLangiumServices = + TypirServices & TypirLangiumAddedServices; -export type PartialTypirLangiumServices = DeepPartial> +export type PartialTypirLangiumServices = + DeepPartial>; /** * Creates a module that replaces some implementations of the core Typir services in order to be used with Langium. * @param langiumServices Typir-Langium needs to interact with the Langium lifecycle * @returns (only) the replaced implementations */ -export function createLangiumSpecificTypirServicesModule(langiumServices: LangiumSharedCoreServices): Module> { +export function createLangiumSpecificTypirServicesModule( + langiumServices: LangiumSharedCoreServices, +): Module> { return { Printer: () => new LangiumProblemPrinter(), - Language: () => { throw new Error('Use new LangiumLanguageService(undefined), and replace "undefined" by the generated XXXAstReflection!'); }, // to be replaced later + Language: () => { + throw new Error( + 'Use new LangiumLanguageService(undefined), and replace "undefined" by the generated XXXAstReflection!', + ); + }, // to be replaced later caching: { - LanguageNodeInference: () => new LangiumLanguageNodeInferenceCaching(langiumServices), + LanguageNodeInference: () => + new LangiumLanguageNodeInferenceCaching(langiumServices), }, }; } @@ -54,21 +89,30 @@ export function createLangiumSpecificTypirServicesModule(langiumServices: Langiu * @param langiumServices Typir-Langium needs to interact with the Langium lifecycle * @returns an implementation for each of the additional Tyir-Langium services */ -export function createDefaultTypirLangiumServicesModule(langiumServices: LangiumSharedCoreServices): Module, TypirLangiumAddedServices> { +export function createDefaultTypirLangiumServicesModule< + AstTypes extends LangiumAstTypes, +>( + langiumServices: LangiumSharedCoreServices, +): Module, TypirLangiumAddedServices> { return { - Inference: (typirServices) => new DefaultLangiumTypeInferenceCollector(typirServices), + Inference: (typirServices) => + new DefaultLangiumTypeInferenceCollector(typirServices), langium: { - TypeCreator: (typirServices) => new DefaultLangiumTypeCreator(typirServices, langiumServices), - TypeSystemDefinition: () => { throw new Error('The type system needs to be specified!'); }, // to be replaced later + TypeCreator: (typirServices) => + new DefaultLangiumTypeCreator(typirServices, langiumServices), + TypeSystemDefinition: () => { + throw new Error("The type system needs to be specified!"); + }, // to be replaced later }, validation: { - Collector: (typirServices) => new DefaultLangiumValidationCollector(typirServices), - TypeValidation: (typirServices) => new DefaultLangiumTypirValidator(typirServices), + Collector: (typirServices) => + new DefaultLangiumValidationCollector(typirServices), + TypeValidation: (typirServices) => + new DefaultLangiumTypirValidator(typirServices), }, }; } - /** * This is the entry point to create Typir-Langium services to simplify type checking for DSLs developed with Langium, * the language workbench for textual domain-specific languages (DSLs) in the web (https://langium.org/). @@ -80,7 +124,9 @@ export function createDefaultTypirLangiumServicesModule( - langiumServices: LangiumSharedCoreServices, reflection: AbstractAstReflection, typeSystemDefinition: LangiumTypeSystemDefinition, + langiumServices: LangiumSharedCoreServices, + reflection: AbstractAstReflection, + typeSystemDefinition: LangiumTypeSystemDefinition, customization1: Module> = {}, customization2: Module> = {}, customization3: Module> = {}, @@ -106,8 +152,12 @@ export function createTypirLangiumServices( ); } - -export function initializeLangiumTypirServices(langiumServices: LangiumDefaultCoreServices, typirServices: TypirLangiumServices): void { +export function initializeLangiumTypirServices< + AstTypes extends LangiumAstTypes, +>( + langiumServices: LangiumDefaultCoreServices, + typirServices: TypirLangiumServices, +): void { // register the type-related validations of Typir at the Langium validation registry registerTypirValidationChecks(langiumServices, typirServices); diff --git a/packages/typir-langium/src/utils/typir-langium-utils.ts b/packages/typir-langium/src/utils/typir-langium-utils.ts index f0ca9349..90997af3 100644 --- a/packages/typir-langium/src/utils/typir-langium-utils.ts +++ b/packages/typir-langium/src/utils/typir-langium-utils.ts @@ -4,8 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, LangiumDocument, LangiumSharedCoreServices, URI } from 'langium'; -import { assertTrue } from 'typir'; +import { + AstNode, + AstUtils, + LangiumDocument, + LangiumSharedCoreServices, + URI, +} from "langium"; +import { assertTrue } from "typir"; export function getDocumentKeyForURI(document: URI): string { return document.toString(); @@ -26,10 +32,9 @@ export async function deleteAllDocuments(services: LangiumSharedCoreServices) { .toArray(); await services.workspace.DocumentBuilder.update( [], // update no documents - docsToDelete // delete all documents + docsToDelete, // delete all documents ); } - /** Generic super type for the Langium-generated XXXAstType. */ export type LangiumAstTypes = Record; diff --git a/packages/typir/src/graph/graph-algorithms.ts b/packages/typir/src/graph/graph-algorithms.ts index 9cef0659..71f97268 100644 --- a/packages/typir/src/graph/graph-algorithms.ts +++ b/packages/typir/src/graph/graph-algorithms.ts @@ -4,19 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypirServices } from '../typir.js'; -import { TypeEdge } from './type-edge.js'; -import { TypeGraph } from './type-graph.js'; -import { Type } from './type-node.js'; +import { TypirServices } from "../typir.js"; +import { TypeEdge } from "./type-edge.js"; +import { TypeGraph } from "./type-graph.js"; +import { Type } from "./type-node.js"; /** * Graph algorithms to do calculations on the type graph. * All algorithms are robust regarding cycles. */ export interface GraphAlgorithms { - collectReachableTypes(from: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): Set; - existsEdgePath(from: Type, to: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): boolean; - getEdgePath(from: Type, to: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): TypeEdge[]; + collectReachableTypes( + from: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): Set; + existsEdgePath( + from: Type, + to: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): boolean; + getEdgePath( + from: Type, + to: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): TypeEdge[]; } export class DefaultGraphAlgorithms implements GraphAlgorithms { @@ -26,15 +40,24 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { this.graph = services.infrastructure.Graph; } - collectReachableTypes(from: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): Set { + collectReachableTypes( + from: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): Set { const result: Set = new Set(); const remainingToCheck: Type[] = [from]; while (remainingToCheck.length > 0) { const current = remainingToCheck.pop()!; - const outgoingEdges = $relations.flatMap(r => current.getOutgoingEdges(r)); + const outgoingEdges = $relations.flatMap((r) => + current.getOutgoingEdges(r), + ); for (const edge of outgoingEdges) { - if (edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge))) { + if ( + edge.cachingInformation === "LINK_EXISTS" && + (filterEdges === undefined || filterEdges(edge)) + ) { if (result.has(edge.to)) { // already checked } else { @@ -48,7 +71,12 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { return result; } - existsEdgePath(from: Type, to: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): boolean { + existsEdgePath( + from: Type, + to: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): boolean { const visited: Set = new Set(); const stack: Type[] = [from]; @@ -56,9 +84,14 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { const current = stack.pop()!; visited.add(current); - const outgoingEdges = $relations.flatMap(r => current.getOutgoingEdges(r)); + const outgoingEdges = $relations.flatMap((r) => + current.getOutgoingEdges(r), + ); for (const edge of outgoingEdges) { - if (edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge))) { + if ( + edge.cachingInformation === "LINK_EXISTS" && + (filterEdges === undefined || filterEdges(edge)) + ) { if (edge.to === to) { /* It was possible to reach our goal type using this path. * Base case that also catches the case in which start and end are the same @@ -81,17 +114,27 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { return false; } - getEdgePath(from: Type, to: Type, $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean): TypeEdge[] { - const visited: Map = new Map(); // the edge from the parent to the current node + getEdgePath( + from: Type, + to: Type, + $relations: Array, + filterEdges?: (edgr: TypeEdge) => boolean, + ): TypeEdge[] { + const visited: Map = new Map(); // the edge from the parent to the current node visited.set(from, undefined); const stack: Type[] = [from]; while (stack.length > 0) { const current = stack.pop()!; - const outgoingEdges = $relations.flatMap(r => current.getOutgoingEdges(r)); + const outgoingEdges = $relations.flatMap((r) => + current.getOutgoingEdges(r), + ); for (const edge of outgoingEdges) { - if (edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge))) { + if ( + edge.cachingInformation === "LINK_EXISTS" && + (filterEdges === undefined || filterEdges(edge)) + ) { if (edge.to === to) { /* It was possible to reach our goal type using this path. * Base case that also catches the case in which start and end are the same @@ -122,5 +165,4 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { // Fall through means that we could not reach the goal type return []; } - } diff --git a/packages/typir/src/graph/type-edge.ts b/packages/typir/src/graph/type-edge.ts index d8047869..22f656dd 100644 --- a/packages/typir/src/graph/type-edge.ts +++ b/packages/typir/src/graph/type-edge.ts @@ -4,8 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EdgeCachingInformation } from '../services/caching.js'; -import { Type } from './type-node.js'; +import { EdgeCachingInformation } from "../services/caching.js"; +import { Type } from "./type-node.js"; /** * An edge has a direction (from --> to) and can be querried from both types (incomingEdge, outgoingEdge). @@ -29,5 +29,9 @@ export interface TypeEdge { } export function isTypeEdge(edge: unknown): edge is TypeEdge { - return typeof edge === 'object' && edge !== null && typeof (edge as TypeEdge).$relation === 'string'; + return ( + typeof edge === "object" && + edge !== null && + typeof (edge as TypeEdge).$relation === "string" + ); } diff --git a/packages/typir/src/graph/type-graph.ts b/packages/typir/src/graph/type-graph.ts index e7599c75..4b7960d0 100644 --- a/packages/typir/src/graph/type-graph.ts +++ b/packages/typir/src/graph/type-graph.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EdgeCachingInformation } from '../services/caching.js'; -import { assertTrue, removeFromArray } from '../utils/utils.js'; -import { TypeEdge } from './type-edge.js'; -import { Type } from './type-node.js'; +import { EdgeCachingInformation } from "../services/caching.js"; +import { assertTrue, removeFromArray } from "../utils/utils.js"; +import { TypeEdge } from "./type-edge.js"; +import { Type } from "./type-node.js"; /** * Each Typir instance has one single type graph. @@ -18,7 +18,6 @@ import { Type } from './type-node.js'; * Graph algorithms will need to filter the required edges regarding $relation. */ export class TypeGraph { - protected readonly nodes: Map = new Map(); // type name => Type protected readonly edges: TypeEdge[] = []; @@ -32,7 +31,7 @@ export class TypeGraph { */ addNode(type: Type, key?: string): void { if (!key) { - assertTrue(type.isInStateOrLater('Identifiable')); // the key of the type must be available! + assertTrue(type.isInStateOrLater("Identifiable")); // the key of the type must be available! } const mapKey = key ?? type.getIdentifier(); if (this.nodes.has(mapKey)) { @@ -43,7 +42,9 @@ export class TypeGraph { } } else { this.nodes.set(mapKey, type); - this.listeners.forEach(listener => listener.onAddedType?.call(listener, type, mapKey)); + this.listeners.forEach((listener) => + listener.onAddedType?.call(listener, type, mapKey), + ); } } @@ -58,12 +59,20 @@ export class TypeGraph { removeNode(typeToRemove: Type, key?: string): void { const mapKey = key ?? typeToRemove.getIdentifier(); // remove all edges which are connected to the type to remove - typeToRemove.getAllIncomingEdges().forEach(e => this.removeEdge(e)); - typeToRemove.getAllOutgoingEdges().forEach(e => this.removeEdge(e)); + typeToRemove.getAllIncomingEdges().forEach((e) => this.removeEdge(e)); + typeToRemove.getAllOutgoingEdges().forEach((e) => this.removeEdge(e)); // remove the type itself const contained = this.nodes.delete(mapKey); if (contained) { - this.listeners.slice().forEach(listener => listener.onRemovedType?.call(listener, typeToRemove, mapKey)); + this.listeners + .slice() + .forEach((listener) => + listener.onRemovedType?.call( + listener, + typeToRemove, + mapKey, + ), + ); typeToRemove.dispose(); } else { throw new Error(`Type does not exist: ${mapKey}`); @@ -83,8 +92,14 @@ export class TypeGraph { addEdge(edge: TypeEdge): void { // check constraints: no duplicated edges (same values for: from, to, $relation) - if (edge.from.getOutgoingEdges(edge.$relation).some(e => e.to === edge.to)) { - throw new Error(`There is already a '${edge.$relation}' edge from '${edge.from.getName()}' to '${edge.to.getName()}'.`); + if ( + edge.from + .getOutgoingEdges(edge.$relation) + .some((e) => e.to === edge.to) + ) { + throw new Error( + `There is already a '${edge.$relation}' edge from '${edge.from.getName()}' to '${edge.to.getName()}'.`, + ); } // TODO what about the other direction for bidirectional edges? for now, the user has to ensure no duplicates here! @@ -94,7 +109,9 @@ export class TypeGraph { edge.to.addIncomingEdge(edge); edge.from.addOutgoingEdge(edge); - this.listeners.forEach(listener => listener.onAddedEdge?.call(listener, edge)); + this.listeners.forEach((listener) => + listener.onAddedEdge?.call(listener, edge), + ); } removeEdge(edge: TypeEdge): void { @@ -103,22 +120,43 @@ export class TypeGraph { edge.from.removeOutgoingEdge(edge); if (removeFromArray(edge, this.edges)) { - this.listeners.forEach(listener => listener.onRemovedEdge?.call(listener, edge)); + this.listeners.forEach((listener) => + listener.onRemovedEdge?.call(listener, edge), + ); } else { throw new Error(`Edge does not exist: ${edge.$relation}`); } } - getUnidirectionalEdge(from: Type, to: Type, $relation: T['$relation'], cachingMode: EdgeCachingInformation = 'LINK_EXISTS'): T | undefined { - return from.getOutgoingEdges($relation).find(edge => edge.to === to && edge.cachingInformation === cachingMode); + getUnidirectionalEdge( + from: Type, + to: Type, + $relation: T["$relation"], + cachingMode: EdgeCachingInformation = "LINK_EXISTS", + ): T | undefined { + return from + .getOutgoingEdges($relation) + .find( + (edge) => + edge.to === to && edge.cachingInformation === cachingMode, + ); } - getBidirectionalEdge(from: Type, to: Type, $relation: T['$relation'], cachingMode: EdgeCachingInformation = 'LINK_EXISTS'): T | undefined { + getBidirectionalEdge( + from: Type, + to: Type, + $relation: T["$relation"], + cachingMode: EdgeCachingInformation = "LINK_EXISTS", + ): T | undefined { // for bidirectional edges, check outgoing and incoming edges, since the graph contains only a single edge! - return from.getEdges($relation).find(edge => edge.to === to && edge.cachingInformation === cachingMode); + return from + .getEdges($relation) + .find( + (edge) => + edge.to === to && edge.cachingInformation === cachingMode, + ); } - // register listeners for changed types/edges in the type graph addListener(listener: TypeGraphListener): void { @@ -128,9 +166,7 @@ export class TypeGraph { removeFromArray(listener, this.listeners); } - // add reusable graph algorithms here (or introduce a new service for graph algorithms which might be easier to customize/exchange) - } export type TypeGraphListener = Partial<{ @@ -138,4 +174,4 @@ export type TypeGraphListener = Partial<{ onRemovedType(type: Type, key: string): void; onAddedEdge(edge: TypeEdge): void; onRemovedEdge(edge: TypeEdge): void; -}> +}>; diff --git a/packages/typir/src/graph/type-node.ts b/packages/typir/src/graph/type-node.ts index 89620b1d..430ec97b 100644 --- a/packages/typir/src/graph/type-node.ts +++ b/packages/typir/src/graph/type-node.ts @@ -4,12 +4,19 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeReference } from '../initialization/type-reference.js'; -import { WaitingForIdentifiableAndCompletedTypeReferences, WaitingForInvalidTypeReferences } from '../initialization/type-waiting.js'; -import { Kind, isKind } from '../kinds/kind.js'; -import { TypirProblem } from '../utils/utils-definitions.js'; -import { assertTrue, assertUnreachable, removeFromArray } from '../utils/utils.js'; -import { TypeEdge } from './type-edge.js'; +import { TypeReference } from "../initialization/type-reference.js"; +import { + WaitingForIdentifiableAndCompletedTypeReferences, + WaitingForInvalidTypeReferences, +} from "../initialization/type-waiting.js"; +import { Kind, isKind } from "../kinds/kind.js"; +import { TypirProblem } from "../utils/utils-definitions.js"; +import { + assertTrue, + assertUnreachable, + removeFromArray, +} from "../utils/utils.js"; +import { TypeEdge } from "./type-edge.js"; /** * The transitions between the states of a type are depicted as state machine: @@ -26,7 +33,7 @@ stateDiagram-v2 * A state is 'Invalid' otherwise. * 'Invalid' is made explicit, since it might require less dependencies than 'Completed' and therefore speed-ups the resolution of dependencies. */ -export type TypeInitializationState = 'Invalid' | 'Identifiable' | 'Completed'; +export type TypeInitializationState = "Invalid" | "Identifiable" | "Completed"; export interface PreconditionsForInitializationState { referencesToBeIdentifiable?: Array>; // or later/more @@ -72,12 +79,14 @@ export abstract class Type { */ readonly associatedLanguageNode: unknown | undefined; - constructor(identifier: string | undefined, typeDetails: TypeDetails) { + constructor( + identifier: string | undefined, + typeDetails: TypeDetails, + ) { this.identifier = identifier; this.associatedLanguageNode = typeDetails.associatedLanguageNode; } - /** * Identifiers must be unique and stable for all types known in a single Typir instance, since they are used as key to store types in maps. * Identifiers might have a naming schema for calculatable values. @@ -106,11 +115,9 @@ export abstract class Type { */ abstract getUserRepresentation(): string; - - // store the state of the initialization process of this type - protected initializationState: TypeInitializationState = 'Invalid'; + protected initializationState: TypeInitializationState = "Invalid"; getInitializationState(): TypeInitializationState { return this.initializationState; @@ -118,17 +125,23 @@ export abstract class Type { protected assertState(expectedState: TypeInitializationState): void { if (this.isInState(expectedState) === false) { - throw new Error(`The current state of type '${this.identifier}' is ${this.initializationState}, but ${expectedState} is expected.`); + throw new Error( + `The current state of type '${this.identifier}' is ${this.initializationState}, but ${expectedState} is expected.`, + ); } } protected assertNotState(expectedState: TypeInitializationState): void { if (this.isNotInState(expectedState) === false) { - throw new Error(`The current state of type '${this.identifier}' is ${this.initializationState}, but this state is not expected.`); + throw new Error( + `The current state of type '${this.identifier}' is ${this.initializationState}, but this state is not expected.`, + ); } } protected assertStateOrLater(expectedState: TypeInitializationState): void { if (this.isInStateOrLater(expectedState) === false) { - throw new Error(`The current state of type '${this.identifier}' is ${this.initializationState}, but this state is not expected.`); + throw new Error( + `The current state of type '${this.identifier}' is ${this.initializationState}, but this state is not expected.`, + ); } } @@ -140,34 +153,36 @@ export abstract class Type { } isInStateOrLater(state: TypeInitializationState): boolean { switch (state) { - case 'Invalid': + case "Invalid": return true; - case 'Identifiable': - return this.initializationState !== 'Invalid'; - case 'Completed': - return this.initializationState === 'Completed'; + case "Identifiable": + return this.initializationState !== "Invalid"; + case "Completed": + return this.initializationState === "Completed"; default: assertUnreachable(state); } } - // manage listeners for updates of the initialization state protected stateListeners: TypeStateListener[] = []; - addListener(newListeners: TypeStateListener, informIfNotInvalidAnymore: boolean): void { + addListener( + newListeners: TypeStateListener, + informIfNotInvalidAnymore: boolean, + ): void { this.stateListeners.push(newListeners); if (informIfNotInvalidAnymore) { const currentState = this.getInitializationState(); switch (currentState) { - case 'Invalid': + case "Invalid": // don't inform about the Invalid state! break; - case 'Identifiable': + case "Identifiable": newListeners.onSwitchedToIdentifiable(this); break; - case 'Completed': + case "Completed": newListeners.onSwitchedToIdentifiable(this); // inform about both Identifiable and Completed! newListeners.onSwitchedToCompleted(this); break; @@ -202,18 +217,18 @@ export abstract class Type { */ protected defineTheInitializationProcessOfThisType(preconditions: { /** Contains only those TypeReferences which are required to do the initialization. */ - preconditionsForIdentifiable?: PreconditionsForInitializationState, + preconditionsForIdentifiable?: PreconditionsForInitializationState; /** Contains only those TypeReferences which are required to do the completion. * TypeReferences which are required only for the initialization, but not for the completion, * don't need to be repeated here, since the completion is done only after the initialization. */ - preconditionsForCompleted?: PreconditionsForInitializationState, + preconditionsForCompleted?: PreconditionsForInitializationState; /** Must contain all(!) TypeReferences of a type. */ - referencesRelevantForInvalidation?: Array>, + referencesRelevantForInvalidation?: Array>; /** typical use cases: calculate the identifier, register inference rules for the type object already now! */ - onIdentifiable?: () => void, + onIdentifiable?: () => void; /** typical use cases: do some internal checks for the completed properties */ - onCompleted?: () => void, - onInvalidated?: () => void, + onCompleted?: () => void; + onInvalidated?: () => void; }): void { // store the reactions this.onIdentification = preconditions.onIdentifiable ?? (() => {}); @@ -221,16 +236,18 @@ export abstract class Type { this.onInvalidation = preconditions.onInvalidated ?? (() => {}); // preconditions for Identifiable - this.waitForIdentifiable = new WaitingForIdentifiableAndCompletedTypeReferences( - preconditions.preconditionsForIdentifiable?.referencesToBeIdentifiable, - preconditions.preconditionsForIdentifiable?.referencesToBeCompleted, - ); + this.waitForIdentifiable = + new WaitingForIdentifiableAndCompletedTypeReferences( + preconditions.preconditionsForIdentifiable?.referencesToBeIdentifiable, + preconditions.preconditionsForIdentifiable?.referencesToBeCompleted, + ); this.waitForIdentifiable.addTypesToIgnoreForCycles(new Set([this])); // start of the principle: children don't need to wait for their parents // preconditions for Completed - this.waitForCompleted = new WaitingForIdentifiableAndCompletedTypeReferences( - preconditions.preconditionsForCompleted?.referencesToBeIdentifiable, - preconditions.preconditionsForCompleted?.referencesToBeCompleted, - ); + this.waitForCompleted = + new WaitingForIdentifiableAndCompletedTypeReferences( + preconditions.preconditionsForCompleted?.referencesToBeIdentifiable, + preconditions.preconditionsForCompleted?.referencesToBeCompleted, + ); this.waitForCompleted.addTypesToIgnoreForCycles(new Set([this])); // start of the principle: children don't need to wait for their parents // preconditions for Invalid this.waitForInvalid = new WaitingForInvalidTypeReferences( @@ -241,31 +258,37 @@ export abstract class Type { const thisType = this; // invalid --> identifiable - this.waitForIdentifiable.addListener({ - onFulfilled(_waiter) { - thisType.switchFromInvalidToIdentifiable(); - if (thisType.waitForCompleted.isFulfilled()) { - // this is required to ensure the stric order Identifiable --> Completed, since 'waitForCompleted' might already be triggered - thisType.switchFromIdentifiableToCompleted(); - } + this.waitForIdentifiable.addListener( + { + onFulfilled(_waiter) { + thisType.switchFromInvalidToIdentifiable(); + if (thisType.waitForCompleted.isFulfilled()) { + // this is required to ensure the stric order Identifiable --> Completed, since 'waitForCompleted' might already be triggered + thisType.switchFromIdentifiableToCompleted(); + } + }, + onInvalidated(_waiter) { + thisType.switchFromCompleteOrIdentifiableToInvalid(); + }, }, - onInvalidated(_waiter) { - thisType.switchFromCompleteOrIdentifiableToInvalid(); - }, - }, true); // 'true' triggers the initialization process! + true, + ); // 'true' triggers the initialization process! // identifiable --> completed - this.waitForCompleted.addListener({ - onFulfilled(_waiter) { - if (thisType.waitForIdentifiable.isFulfilled()) { - thisType.switchFromIdentifiableToCompleted(); - } else { - // switching will be done later by 'waitForIdentifiable' in order to conform to the stric order Identifiable --> Completed - } + this.waitForCompleted.addListener( + { + onFulfilled(_waiter) { + if (thisType.waitForIdentifiable.isFulfilled()) { + thisType.switchFromIdentifiableToCompleted(); + } else { + // switching will be done later by 'waitForIdentifiable' in order to conform to the stric order Identifiable --> Completed + } + }, + onInvalidated(_waiter) { + thisType.switchFromCompleteOrIdentifiableToInvalid(); + }, }, - onInvalidated(_waiter) { - thisType.switchFromCompleteOrIdentifiableToInvalid(); - }, - }, false); // not required, since 'waitForIdentifiable' will switch to Completed as well! + false, + ); // not required, since 'waitForIdentifiable' will switch to Completed as well! // identifiable/completed --> invalid this.waitForInvalid.addListener(() => { this.switchFromCompleteOrIdentifiableToInvalid(); @@ -277,39 +300,53 @@ export abstract class Type { * Usually there is no need to call this method on your own. * @param additionalTypesToIgnore the new types to ignore during */ - ignoreDependingTypesDuringInitialization(additionalTypesToIgnore: Set): void { - this.waitForIdentifiable.addTypesToIgnoreForCycles(additionalTypesToIgnore); - this.waitForCompleted.addTypesToIgnoreForCycles(additionalTypesToIgnore); + ignoreDependingTypesDuringInitialization( + additionalTypesToIgnore: Set, + ): void { + this.waitForIdentifiable.addTypesToIgnoreForCycles( + additionalTypesToIgnore, + ); + this.waitForCompleted.addTypesToIgnoreForCycles( + additionalTypesToIgnore, + ); } dispose(): void { // clear everything this.stateListeners.splice(0, this.stateListeners.length); - this.waitForInvalid.getWaitForRefsInvalid().forEach(ref => ref.dispose()); + this.waitForInvalid + .getWaitForRefsInvalid() + .forEach((ref) => ref.dispose()); this.waitForIdentifiable.deconstruct(); this.waitForCompleted.deconstruct(); this.waitForInvalid.deconstruct(); } protected switchFromInvalidToIdentifiable(): void { - this.assertState('Invalid'); + this.assertState("Invalid"); this.onIdentification(); - this.initializationState = 'Identifiable'; - this.stateListeners.slice().forEach(listener => listener.onSwitchedToIdentifiable(this)); // slice() prevents issues with removal of listeners during notifications + this.initializationState = "Identifiable"; + this.stateListeners + .slice() + .forEach((listener) => listener.onSwitchedToIdentifiable(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromIdentifiableToCompleted(): void { - this.assertState('Identifiable'); + this.assertState("Identifiable"); this.onCompletion(); - this.initializationState = 'Completed'; - this.stateListeners.slice().forEach(listener => listener.onSwitchedToCompleted(this)); // slice() prevents issues with removal of listeners during notifications + this.initializationState = "Completed"; + this.stateListeners + .slice() + .forEach((listener) => listener.onSwitchedToCompleted(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromCompleteOrIdentifiableToInvalid(): void { - if (this.isNotInState('Invalid')) { + if (this.isNotInState("Invalid")) { this.onInvalidation(); - this.initializationState = 'Invalid'; - this.stateListeners.slice().forEach(listener => listener.onSwitchedToInvalid(this)); // slice() prevents issues with removal of listeners during notifications + this.initializationState = "Invalid"; + this.stateListeners + .slice() + .forEach((listener) => listener.onSwitchedToInvalid(this)); // slice() prevents issues with removal of listeners during notifications // add the types again, since the initialization process started again this.waitForIdentifiable.addTypesToIgnoreForCycles(new Set([this])); this.waitForCompleted.addTypesToIgnoreForCycles(new Set([this])); @@ -318,8 +355,6 @@ export abstract class Type { } } - - /** * Analyzes, whether two types are equal. * @param otherType to be compared with the current type @@ -328,7 +363,6 @@ export abstract class Type { */ abstract analyzeTypeEqualityProblems(otherType: Type): TypirProblem[]; - addIncomingEdge(edge: TypeEdge): void { const key = edge.$relation; if (this.edgesIncoming.has(key)) { @@ -377,13 +411,13 @@ export abstract class Type { return false; } - getIncomingEdges($relation: T['$relation']): T[] { - return this.edgesIncoming.get($relation) as T[] ?? []; + getIncomingEdges($relation: T["$relation"]): T[] { + return (this.edgesIncoming.get($relation) as T[]) ?? []; } - getOutgoingEdges($relation: T['$relation']): T[] { - return this.edgesOutgoing.get($relation) as T[] ?? []; + getOutgoingEdges($relation: T["$relation"]): T[] { + return (this.edgesOutgoing.get($relation) as T[]) ?? []; } - getEdges($relation: T['$relation']): T[] { + getEdges($relation: T["$relation"]): T[] { return [ ...this.getIncomingEdges($relation), ...this.getOutgoingEdges($relation), @@ -397,18 +431,19 @@ export abstract class Type { return Array.from(this.edgesOutgoing.values()).flat(); } getAllEdges(): TypeEdge[] { - return [ - ...this.getAllIncomingEdges(), - ...this.getAllOutgoingEdges(), - ]; + return [...this.getAllIncomingEdges(), ...this.getAllOutgoingEdges()]; } } export function isType(type: unknown): type is Type { - return typeof type === 'object' && type !== null && typeof (type as Type).getIdentifier === 'function' && isKind((type as Type).kind); + return ( + typeof type === "object" && + type !== null && + typeof (type as Type).getIdentifier === "function" && + isKind((type as Type).kind) + ); } - export interface TypeStateListener { onSwitchedToInvalid(type: Type): void; onSwitchedToIdentifiable(type: Type): void; diff --git a/packages/typir/src/index-test.ts b/packages/typir/src/index-test.ts index 18b0d795..794d7138 100644 --- a/packages/typir/src/index-test.ts +++ b/packages/typir/src/index-test.ts @@ -7,4 +7,4 @@ // export all utilities which are using 'vitest' and which are not located in test/ */ but in src/ here // to be imported via 'typir/test' in order not to mix up production code with 'vitest' dependencies -export * from './utils/test-utils.js'; +export * from "./utils/test-utils.js"; diff --git a/packages/typir/src/index.ts b/packages/typir/src/index.ts index 3c4023c5..ef6309de 100644 --- a/packages/typir/src/index.ts +++ b/packages/typir/src/index.ts @@ -4,50 +4,50 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -export * from './typir.js'; -export * from './graph/type-edge.js'; -export * from './graph/type-graph.js'; -export * from './graph/type-node.js'; -export * from './initialization/type-initializer.js'; -export * from './initialization/type-reference.js'; -export * from './initialization/type-selector.js'; -export * from './initialization/type-waiting.js'; -export * from './kinds/bottom/bottom-kind.js'; -export * from './kinds/bottom/bottom-type.js'; -export * from './kinds/class/class-initializer.js'; -export * from './kinds/class/class-kind.js'; -export * from './kinds/class/class-type.js'; -export * from './kinds/class/class-validation.js'; -export * from './kinds/class/top-class-kind.js'; -export * from './kinds/class/top-class-type.js'; -export * from './kinds/fixed-parameters/fixed-parameters-kind.js'; -export * from './kinds/fixed-parameters/fixed-parameters-type.js'; -export * from './kinds/function/function-initializer.js'; -export * from './kinds/function/function-kind.js'; -export * from './kinds/function/function-overloading.js'; -export * from './kinds/function/function-type.js'; -export * from './kinds/function/function-validation-calls.js'; -export * from './kinds/function/function-validation-unique.js'; -export * from './kinds/multiplicity/multiplicity-kind.js'; -export * from './kinds/multiplicity/multiplicity-type.js'; -export * from './kinds/primitive/primitive-kind.js'; -export * from './kinds/primitive/primitive-type.js'; -export * from './kinds/top/top-kind.js'; -export * from './kinds/top/top-type.js'; -export * from './kinds/kind.js'; -export * from './services/assignability.js'; -export * from './services/caching.js'; -export * from './services/conversion.js'; -export * from './services/equality.js'; -export * from './services/inference.js'; -export * from './services/kind-registry.js'; -export * from './services/language.js'; -export * from './services/operator.js'; -export * from './services/printing.js'; -export * from './services/subtype.js'; -export * from './services/validation.js'; -export * from './utils/dependency-injection.js'; -export * from './utils/rule-registration.js'; -export * from './utils/utils.js'; -export * from './utils/utils-definitions.js'; -export * from './utils/utils-type-comparison.js'; +export * from "./typir.js"; +export * from "./graph/type-edge.js"; +export * from "./graph/type-graph.js"; +export * from "./graph/type-node.js"; +export * from "./initialization/type-initializer.js"; +export * from "./initialization/type-reference.js"; +export * from "./initialization/type-selector.js"; +export * from "./initialization/type-waiting.js"; +export * from "./kinds/bottom/bottom-kind.js"; +export * from "./kinds/bottom/bottom-type.js"; +export * from "./kinds/class/class-initializer.js"; +export * from "./kinds/class/class-kind.js"; +export * from "./kinds/class/class-type.js"; +export * from "./kinds/class/class-validation.js"; +export * from "./kinds/class/top-class-kind.js"; +export * from "./kinds/class/top-class-type.js"; +export * from "./kinds/fixed-parameters/fixed-parameters-kind.js"; +export * from "./kinds/fixed-parameters/fixed-parameters-type.js"; +export * from "./kinds/function/function-initializer.js"; +export * from "./kinds/function/function-kind.js"; +export * from "./kinds/function/function-overloading.js"; +export * from "./kinds/function/function-type.js"; +export * from "./kinds/function/function-validation-calls.js"; +export * from "./kinds/function/function-validation-unique.js"; +export * from "./kinds/multiplicity/multiplicity-kind.js"; +export * from "./kinds/multiplicity/multiplicity-type.js"; +export * from "./kinds/primitive/primitive-kind.js"; +export * from "./kinds/primitive/primitive-type.js"; +export * from "./kinds/top/top-kind.js"; +export * from "./kinds/top/top-type.js"; +export * from "./kinds/kind.js"; +export * from "./services/assignability.js"; +export * from "./services/caching.js"; +export * from "./services/conversion.js"; +export * from "./services/equality.js"; +export * from "./services/inference.js"; +export * from "./services/kind-registry.js"; +export * from "./services/language.js"; +export * from "./services/operator.js"; +export * from "./services/printing.js"; +export * from "./services/subtype.js"; +export * from "./services/validation.js"; +export * from "./utils/dependency-injection.js"; +export * from "./utils/rule-registration.js"; +export * from "./utils/utils.js"; +export * from "./utils/utils-definitions.js"; +export * from "./utils/utils-type-comparison.js"; diff --git a/packages/typir/src/initialization/type-initializer.ts b/packages/typir/src/initialization/type-initializer.ts index e14e3dd1..f43e8144 100644 --- a/packages/typir/src/initialization/type-initializer.ts +++ b/packages/typir/src/initialization/type-initializer.ts @@ -4,8 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; export type TypeInitializerListener = (type: T) => void; @@ -38,7 +38,7 @@ export abstract class TypeInitializer { protected producedType(newType: T): T { const key = newType.getIdentifier(); if (!key) { - throw new Error('missing identifier!'); + throw new Error("missing identifier!"); } const existingType = this.services.infrastructure.Graph.getType(key); if (existingType) { @@ -51,7 +51,9 @@ export abstract class TypeInitializer { } // inform and clear all listeners - this.listeners.slice().forEach(listener => listener(this.typeToReturn!)); + this.listeners + .slice() + .forEach((listener) => listener(this.typeToReturn!)); this.listeners = []; // clear the list of listeners, since they will not be informed again // return the created/identified type @@ -59,7 +61,7 @@ export abstract class TypeInitializer { } // TODO using this type feels wrong, but without this approach, it seems not to work ... - abstract getTypeInitial(): T + abstract getTypeInitial(): T; getTypeFinal(): T | undefined { return this.typeToReturn; diff --git a/packages/typir/src/initialization/type-reference.ts b/packages/typir/src/initialization/type-reference.ts index 577b330e..8db2fa27 100644 --- a/packages/typir/src/initialization/type-reference.ts +++ b/packages/typir/src/initialization/type-reference.ts @@ -4,12 +4,16 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../graph/type-graph.js'; -import { Type } from '../graph/type-node.js'; -import { TypeInferenceCollectorListener, TypeInferenceRule, TypeInferenceRuleOptions } from '../services/inference.js'; -import { TypirServices } from '../typir.js'; -import { removeFromArray } from '../utils/utils.js'; -import { TypeSelector } from './type-selector.js'; +import { TypeGraphListener } from "../graph/type-graph.js"; +import { Type } from "../graph/type-node.js"; +import { + TypeInferenceCollectorListener, + TypeInferenceRule, + TypeInferenceRuleOptions, +} from "../services/inference.js"; +import { TypirServices } from "../typir.js"; +import { removeFromArray } from "../utils/utils.js"; +import { TypeSelector } from "./type-selector.js"; /** * A listener for TypeReferences, who will be informed about the resolved/found type of the current TypeReference. @@ -21,7 +25,10 @@ export interface TypeReferenceListener { * @param resolvedType Usually the resolved type is either 'Identifiable' or 'Completed', * in rare cases this type might be 'Invalid', e.g. if there are corresponding inference rules or TypeInitializers. */ - onTypeReferenceResolved(reference: TypeReference, resolvedType: T): void; + onTypeReferenceResolved( + reference: TypeReference, + resolvedType: T, + ): void; /** * Informs when the type of the reference is invalidated/removed. @@ -29,7 +36,10 @@ export interface TypeReferenceListener { * @param previousType undefined occurs in the special case, that the TypeReference never resolved a type so far, * but new listeners already want to be informed about the (current) type. */ - onTypeReferenceInvalidated(reference: TypeReference, previousType: T | undefined): void; + onTypeReferenceInvalidated( + reference: TypeReference, + previousType: T | undefined, + ): void; } /** @@ -43,15 +53,22 @@ export interface TypeReferenceListener { * * Once the type is resolved, listeners are notified about this and all following changes of its state. */ -export class TypeReference implements TypeGraphListener, TypeInferenceCollectorListener { +export class TypeReference + implements TypeGraphListener, TypeInferenceCollectorListener +{ protected readonly selector: TypeSelector; protected readonly services: TypirServices; protected resolvedType: T | undefined = undefined; /** These listeners will be informed, whenever the resolved state of this TypeReference switched from undefined to an actual type or from an actual type to undefined. */ - protected readonly listeners: Array> = []; - - constructor(selector: TypeSelector, services: TypirServices) { + protected readonly listeners: Array< + TypeReferenceListener + > = []; + + constructor( + selector: TypeSelector, + services: TypirServices, + ) { this.selector = selector; this.services = services; @@ -92,29 +109,42 @@ export class TypeReference implements Ty * Note that the resolved type might not be completed yet. * @returns the result of the currently executed resolution */ - protected resolve(): 'ALREADY_RESOLVED' | 'SUCCESSFULLY_RESOLVED' | 'RESOLVING_FAILED' { + protected resolve(): + | "ALREADY_RESOLVED" + | "SUCCESSFULLY_RESOLVED" + | "RESOLVING_FAILED" { if (this.resolvedType) { // the type is already resolved => nothing to do - return 'ALREADY_RESOLVED'; + return "ALREADY_RESOLVED"; } // try to resolve the type - const resolvedType = this.services.infrastructure.TypeResolver.tryToResolve(this.selector); + const resolvedType = + this.services.infrastructure.TypeResolver.tryToResolve( + this.selector, + ); if (resolvedType) { // the type is successfully resolved! this.resolvedType = resolvedType; this.stopResolving(); // notify observers - this.listeners.slice().forEach(listener => listener.onTypeReferenceResolved(this, resolvedType)); - return 'SUCCESSFULLY_RESOLVED'; + this.listeners + .slice() + .forEach((listener) => + listener.onTypeReferenceResolved(this, resolvedType), + ); + return "SUCCESSFULLY_RESOLVED"; } else { // the type is not resolved (yet) - return 'RESOLVING_FAILED'; + return "RESOLVING_FAILED"; } } - addListener(listener: TypeReferenceListener, informAboutCurrentState: boolean): void { + addListener( + listener: TypeReferenceListener, + informAboutCurrentState: boolean, + ): void { this.listeners.push(listener); if (informAboutCurrentState) { if (this.resolvedType) { @@ -129,7 +159,6 @@ export class TypeReference implements Ty removeFromArray(listener, this.listeners); } - onAddedType(_addedType: Type, _key: string): void { // after adding a new type, try to resolve the type this.resolve(); // possible performance optimization: is it possible to do this more performant by looking at the "addedType"? @@ -139,17 +168,30 @@ export class TypeReference implements Ty // the resolved type of this TypeReference is removed! if (removedType === this.resolvedType) { // notify observers, that the type reference is broken - this.listeners.slice().forEach(listener => listener.onTypeReferenceInvalidated(this, this.resolvedType!)); + this.listeners + .slice() + .forEach((listener) => + listener.onTypeReferenceInvalidated( + this, + this.resolvedType!, + ), + ); // start resolving the type again this.startResolving(); } } - onAddedInferenceRule(_rule: TypeInferenceRule, _options: TypeInferenceRuleOptions): void { + onAddedInferenceRule( + _rule: TypeInferenceRule, + _options: TypeInferenceRuleOptions, + ): void { // after adding a new inference rule, try to resolve the type this.resolve(); // possible performance optimization: use only the new inference rule to resolve the type } - onRemovedInferenceRule(_rule: TypeInferenceRule, _options: TypeInferenceRuleOptions): void { + onRemovedInferenceRule( + _rule: TypeInferenceRule, + _options: TypeInferenceRuleOptions, + ): void { // empty, since removed inference rules don't help to resolve a type } } diff --git a/packages/typir/src/initialization/type-selector.ts b/packages/typir/src/initialization/type-selector.ts index c67f2f58..917ef858 100644 --- a/packages/typir/src/initialization/type-selector.ts +++ b/packages/typir/src/initialization/type-selector.ts @@ -4,28 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { TypeInitializer } from './type-initializer.js'; -import { TypeReference } from './type-reference.js'; +import { isType, Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { TypeInitializer } from "./type-initializer.js"; +import { TypeReference } from "./type-reference.js"; // TODO find better names: TypeSpecification, TypeDesignation/Designator, ... ? export type BasicTypeSelector = - | T // the wanted type - | string // identifier of the type (to be searched in the type graph/map) - | TypeInitializer // delayed creation of types - | TypeReference // reference to a (maybe delayed) type - | LanguageType // language node to infer the final type from - ; + | T // the wanted type + | string // identifier of the type (to be searched in the type graph/map) + | TypeInitializer // delayed creation of types + | TypeReference // reference to a (maybe delayed) type + | LanguageType; // language node to infer the final type from /** * This TypeScript type defines the possible ways to identify a desired Typir type. */ export type TypeSelector = - | BasicTypeSelector // all base type selectors - | (() => BasicTypeSelector) // all type selectors might be given as functions as well, in order to ease delayed specifications - ; - + | BasicTypeSelector // all base type selectors + | (() => BasicTypeSelector); // all type selectors might be given as functions as well, in order to ease delayed specifications export interface TypeResolvingService { /** @@ -35,7 +32,9 @@ export interface TypeResolvingService { * @param selector the specification for the desired type * @returns the found type; or undefined, if there is no such type in the type system */ - tryToResolve(selector: TypeSelector): T | undefined; + tryToResolve( + selector: TypeSelector, + ): T | undefined; /** * Finds the specified type in the type system. @@ -47,27 +46,36 @@ export interface TypeResolvingService { resolve(selector: TypeSelector): T; } -export class DefaultTypeResolver implements TypeResolvingService { +export class DefaultTypeResolver + implements TypeResolvingService +{ protected readonly services: TypirServices; constructor(services: TypirServices) { this.services = services; } - tryToResolve(selector: TypeSelector): T | undefined { + tryToResolve( + selector: TypeSelector, + ): T | undefined { if (isType(selector)) { // TODO is there a way to explicitly enforce/ensure "as T"? return selector as T; - } else if (typeof selector === 'string') { + } else if (typeof selector === "string") { return this.services.infrastructure.Graph.getType(selector) as T; } else if (selector instanceof TypeInitializer) { return selector.getTypeInitial(); } else if (selector instanceof TypeReference) { return selector.getType(); - } else if (typeof selector === 'function') { + } else if (typeof selector === "function") { // execute the function and try to recursively resolve the returned result again - return this.tryToResolve((selector as () => BasicTypeSelector).call(selector)); - } else { // the selector is of type 'known' => do type inference on it + return this.tryToResolve( + (selector as () => BasicTypeSelector).call( + selector, + ), + ); + } else { + // the selector is of type 'known' => do type inference on it const result = this.services.Inference.inferType(selector); // TODO failures must not be cached, otherwise a type will never be found in the future!! if (isType(result)) { @@ -81,29 +89,46 @@ export class DefaultTypeResolver implements TypeResolvingService(selector: TypeSelector): T { if (isType(selector)) { return selector as T; - } else if (typeof selector === 'string') { + } else if (typeof selector === "string") { return this.handleError( - this.services.infrastructure.Graph.getType(selector) as T | undefined, - `A type with identifier '${selector}' as TypeSelector does not exist in the type graph.` + this.services.infrastructure.Graph.getType(selector) as + | T + | undefined, + `A type with identifier '${selector}' as TypeSelector does not exist in the type graph.`, ); } else if (selector instanceof TypeInitializer) { - return this.handleError(selector.getTypeFinal(), "This TypeInitializer don't provide a type."); + return this.handleError( + selector.getTypeFinal(), + "This TypeInitializer don't provide a type.", + ); } else if (selector instanceof TypeReference) { - return this.handleError(selector.getType(), 'This TypeReference has no resolved type.'); - } else if (typeof selector === 'function') { + return this.handleError( + selector.getType(), + "This TypeReference has no resolved type.", + ); + } else if (typeof selector === "function") { // execute the function and try to recursively resolve the returned result again - return this.resolve((selector as () => BasicTypeSelector).call(selector)); + return this.resolve( + (selector as () => BasicTypeSelector).call( + selector, + ), + ); } else { const result = this.services.Inference.inferType(selector); if (isType(result)) { return result as T; } else { - throw new Error(`For '${this.services.Printer.printLanguageNode(selector, false)}' as TypeSelector, no type can be inferred.`); + throw new Error( + `For '${this.services.Printer.printLanguageNode(selector, false)}' as TypeSelector, no type can be inferred.`, + ); } } } - protected handleError(result: T | undefined, errorMessage: string): T { + protected handleError( + result: T | undefined, + errorMessage: string, + ): T { if (result) { return result as T; } else { diff --git a/packages/typir/src/initialization/type-waiting.ts b/packages/typir/src/initialization/type-waiting.ts index f06cd260..8ed61f49 100644 --- a/packages/typir/src/initialization/type-waiting.ts +++ b/packages/typir/src/initialization/type-waiting.ts @@ -4,13 +4,19 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeStateListener } from '../graph/type-node.js'; -import { removeFromArray, toArray } from '../utils/utils.js'; -import { TypeReferenceListener, TypeReference } from './type-reference.js'; - -export interface WaitingForIdentifiableAndCompletedTypeReferencesListener { - onFulfilled(waiter: WaitingForIdentifiableAndCompletedTypeReferences): void; - onInvalidated(waiter: WaitingForIdentifiableAndCompletedTypeReferences): void; +import { Type, TypeStateListener } from "../graph/type-node.js"; +import { removeFromArray, toArray } from "../utils/utils.js"; +import { TypeReferenceListener, TypeReference } from "./type-reference.js"; + +export interface WaitingForIdentifiableAndCompletedTypeReferencesListener< + T extends Type, +> { + onFulfilled( + waiter: WaitingForIdentifiableAndCompletedTypeReferences, + ): void; + onInvalidated( + waiter: WaitingForIdentifiableAndCompletedTypeReferences, + ): void; } /** @@ -18,7 +24,9 @@ export interface WaitingForIdentifiableAndCompletedTypeReferencesListener implements TypeReferenceListener, TypeStateListener { +export class WaitingForIdentifiableAndCompletedTypeReferences + implements TypeReferenceListener, TypeStateListener +{ /** Remembers whether all TypeReferences are in the desired states (true) or not (false). */ protected fulfilled: boolean = false; /** This is required for cyclic type definitions: @@ -31,37 +39,49 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im protected typesToIgnoreForCycles: Set = new Set(); /** These TypeReferences must be in the states Identifiable or Completed, before the listeners are informed */ - protected readonly waitForRefsIdentified: Array> | undefined; + protected readonly waitForRefsIdentified: + | Array> + | undefined; /** These TypeReferences must be in the state Completed, before the listeners are informed */ - protected readonly waitForRefsCompleted: Array> | undefined; + protected readonly waitForRefsCompleted: + | Array> + | undefined; /** These listeners will be informed once, when all TypeReferences are in the desired state. * If some of these TypeReferences are invalid (the listeners will not be informed about this) and later in the desired state again, * the listeners will be informed again, and so on. */ - protected readonly listeners: Array> = []; + protected readonly listeners: Array< + WaitingForIdentifiableAndCompletedTypeReferencesListener + > = []; constructor( waitForRefsToBeIdentified: Array> | undefined, waitForRefsToBeCompleted: Array> | undefined, ) { - // remember the relevant TypeReferences to wait for this.waitForRefsIdentified = waitForRefsToBeIdentified; this.waitForRefsCompleted = waitForRefsToBeCompleted; // register to get updates for the relevant TypeReferences - toArray(this.waitForRefsIdentified).forEach(ref => ref.addListener(this, true)); // 'true' calls 'checkIfFulfilled()' to check, whether everything might already be fulfilled - toArray(this.waitForRefsCompleted).forEach(ref => ref.addListener(this, true)); + toArray(this.waitForRefsIdentified).forEach((ref) => + ref.addListener(this, true), + ); // 'true' calls 'checkIfFulfilled()' to check, whether everything might already be fulfilled + toArray(this.waitForRefsCompleted).forEach((ref) => + ref.addListener(this, true), + ); } deconstruct(): void { this.listeners.splice(0, this.listeners.length); - this.waitForRefsIdentified?.forEach(ref => ref.removeListener(this)); - this.waitForRefsCompleted?.forEach(ref => ref.removeListener(this)); + this.waitForRefsIdentified?.forEach((ref) => ref.removeListener(this)); + this.waitForRefsCompleted?.forEach((ref) => ref.removeListener(this)); this.typesToIgnoreForCycles.clear(); } - addListener(newListener: WaitingForIdentifiableAndCompletedTypeReferencesListener, informAboutCurrentState: boolean): void { + addListener( + newListener: WaitingForIdentifiableAndCompletedTypeReferencesListener, + informAboutCurrentState: boolean, + ): void { this.listeners.push(newListener); // inform the new listener if (informAboutCurrentState) { @@ -73,7 +93,9 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im } } - removeListener(listenerToRemove: WaitingForIdentifiableAndCompletedTypeReferencesListener): void { + removeListener( + listenerToRemove: WaitingForIdentifiableAndCompletedTypeReferencesListener, + ): void { removeFromArray(listenerToRemove, this.listeners); } @@ -99,21 +121,25 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im } else { // propagate the new types to ignore recursively to all direct and indirect referenced types ... // ... which should be identifiable (or completed) - for (const ref of (this.waitForRefsIdentified ?? [])) { + for (const ref of this.waitForRefsIdentified ?? []) { const refType = ref.getType(); - if (refType?.isInStateOrLater('Identifiable')) { + if (refType?.isInStateOrLater("Identifiable")) { // this reference is already ready } else { - refType?.ignoreDependingTypesDuringInitialization(newTypesToIgnore); + refType?.ignoreDependingTypesDuringInitialization( + newTypesToIgnore, + ); } } // ... which should be completed - for (const ref of (this.waitForRefsCompleted ?? [])) { + for (const ref of this.waitForRefsCompleted ?? []) { const refType = ref.getType(); - if (refType?.isInStateOrLater('Completed')) { + if (refType?.isInStateOrLater("Completed")) { // this reference is already ready } else { - refType?.ignoreDependingTypesDuringInitialization(newTypesToIgnore); + refType?.ignoreDependingTypesDuringInitialization( + newTypesToIgnore, + ); } } @@ -122,16 +148,24 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im } } - onTypeReferenceResolved(_reference: TypeReference, resolvedType: Type): void { + onTypeReferenceResolved( + _reference: TypeReference, + resolvedType: Type, + ): void { // inform the referenced type about the types to ignore for completion, so that the type could switch to its next phase (if needed) - resolvedType.ignoreDependingTypesDuringInitialization(this.typesToIgnoreForCycles); + resolvedType.ignoreDependingTypesDuringInitialization( + this.typesToIgnoreForCycles, + ); resolvedType.addListener(this, false); // check, whether all TypeReferences are resolved and the resolved types are in the expected state this.checkIfFulfilled(); // TODO is a more performant solution possible, e.g. by counting or using "resolvedType"? } - onTypeReferenceInvalidated(_reference: TypeReference, previousType: Type | undefined): void { + onTypeReferenceInvalidated( + _reference: TypeReference, + previousType: Type | undefined, + ): void { // since at least one TypeReference was reset, the listeners might be informed (again), when all TypeReferences reached the desired state (again) this.switchToNotFulfilled(); if (previousType) { @@ -162,7 +196,11 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im for (const ref of toArray(this.waitForRefsIdentified)) { const refType = ref.getType(); - if (refType && (refType.isInStateOrLater('Identifiable') || this.typesToIgnoreForCycles.has(refType))) { + if ( + refType && + (refType.isInStateOrLater("Identifiable") || + this.typesToIgnoreForCycles.has(refType)) + ) { // that is fine } else { return; @@ -170,7 +208,11 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im } for (const ref of toArray(this.waitForRefsCompleted)) { const refType = ref.getType(); - if (refType && (refType.isInStateOrLater('Completed') || this.typesToIgnoreForCycles.has(refType))) { + if ( + refType && + (refType.isInStateOrLater("Completed") || + this.typesToIgnoreForCycles.has(refType)) + ) { // that is fine } else { return; @@ -179,7 +221,9 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im // everything is fine now! => inform all listeners this.fulfilled = true; // don't inform the listeners again - this.listeners.slice().forEach(listener => listener.onFulfilled(this)); // slice() prevents issues with removal of listeners during notifications + this.listeners + .slice() + .forEach((listener) => listener.onFulfilled(this)); // slice() prevents issues with removal of listeners during notifications this.typesToIgnoreForCycles.clear(); // otherwise deleted types remain in this Set forever } @@ -187,7 +231,9 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im // since at least one TypeReference was reset, the listeners might be informed (again), when all TypeReferences reached the desired state (again) if (this.fulfilled) { this.fulfilled = false; - this.listeners.slice().forEach(listener => listener.onInvalidated(this)); // slice() prevents issues with removal of listeners during notifications + this.listeners + .slice() + .forEach((listener) => listener.onInvalidated(this)); // slice() prevents issues with removal of listeners during notifications } else { // already not fulfilled => nothing to do now } @@ -198,35 +244,45 @@ export class WaitingForIdentifiableAndCompletedTypeReferences im } } -export type WaitingForInvalidTypeReferencesListener = (waiter: WaitingForInvalidTypeReferences) => void; +export type WaitingForInvalidTypeReferencesListener = ( + waiter: WaitingForInvalidTypeReferences, +) => void; -export class WaitingForInvalidTypeReferences implements TypeReferenceListener { +export class WaitingForInvalidTypeReferences + implements TypeReferenceListener +{ protected counterInvalid: number; // just count the number of invalid TypeReferences // At least one of the given TypeReferences must be in the state Invalid. protected readonly waitForRefsInvalid: Array>; /** These listeners will be informed, when all TypeReferences are in the desired state. */ - protected readonly listeners: Array> = []; - - constructor( - waitForRefsToBeInvalid: Array>, - ) { + protected readonly listeners: Array< + WaitingForInvalidTypeReferencesListener + > = []; + constructor(waitForRefsToBeInvalid: Array>) { // remember the relevant TypeReferences this.waitForRefsInvalid = waitForRefsToBeInvalid; - this.counterInvalid = this.waitForRefsInvalid.filter(ref => ref.getType() === undefined || ref.getType()!.isInState('Invalid')).length; + this.counterInvalid = this.waitForRefsInvalid.filter( + (ref) => + ref.getType() === undefined || + ref.getType()!.isInState("Invalid"), + ).length; // register to get updates for the relevant TypeReferences - this.waitForRefsInvalid.forEach(ref => ref.addListener(this, false)); + this.waitForRefsInvalid.forEach((ref) => ref.addListener(this, false)); } deconstruct(): void { this.listeners.splice(0, this.listeners.length); - this.waitForRefsInvalid.forEach(ref => ref.removeListener(this)); + this.waitForRefsInvalid.forEach((ref) => ref.removeListener(this)); } - addListener(newListener: WaitingForInvalidTypeReferencesListener, informIfAlreadyFulfilled: boolean): void { + addListener( + newListener: WaitingForInvalidTypeReferencesListener, + informIfAlreadyFulfilled: boolean, + ): void { this.listeners.push(newListener); // inform new listener, if the state is already reached! if (informIfAlreadyFulfilled && this.isFulfilled()) { @@ -234,23 +290,34 @@ export class WaitingForInvalidTypeReferences implements TypeRefe } } - removeListener(listenerToRemove: WaitingForInvalidTypeReferencesListener): void { + removeListener( + listenerToRemove: WaitingForInvalidTypeReferencesListener, + ): void { removeFromArray(listenerToRemove, this.listeners); } - onTypeReferenceResolved(_reference: TypeReference, _resolvedType: Type): void { + onTypeReferenceResolved( + _reference: TypeReference, + _resolvedType: Type, + ): void { this.counterInvalid--; } - onTypeReferenceInvalidated(_reference: TypeReference, _previousType: Type | undefined): void { + onTypeReferenceInvalidated( + _reference: TypeReference, + _previousType: Type | undefined, + ): void { this.counterInvalid++; if (this.isFulfilled()) { - this.listeners.slice().forEach(listener => listener(this)); + this.listeners.slice().forEach((listener) => listener(this)); } } isFulfilled(): boolean { - return this.counterInvalid === this.waitForRefsInvalid.length && this.waitForRefsInvalid.length >= 1; + return ( + this.counterInvalid === this.waitForRefsInvalid.length && + this.waitForRefsInvalid.length >= 1 + ); } getWaitForRefsInvalid(): Array> { diff --git a/packages/typir/src/kinds/bottom/bottom-kind.ts b/packages/typir/src/kinds/bottom/bottom-kind.ts index 754ee187..c583e7a5 100644 --- a/packages/typir/src/kinds/bottom/bottom-kind.ts +++ b/packages/typir/src/kinds/bottom/bottom-kind.ts @@ -4,17 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { InferCurrentTypeRule, registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; -import { assertTrue } from '../../utils/utils.js'; -import { isKind, Kind } from '../kind.js'; -import { BottomType } from './bottom-type.js'; - -export interface BottomTypeDetails extends TypeDetails { +import { TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { + InferCurrentTypeRule, + registerInferCurrentTypeRules, +} from "../../utils/utils-definitions.js"; +import { assertTrue } from "../../utils/utils.js"; +import { isKind, Kind } from "../kind.js"; +import { BottomType } from "./bottom-type.js"; + +export interface BottomTypeDetails + extends TypeDetails { // empty } -export interface CreateBottomTypeDetails extends BottomTypeDetails { +export interface CreateBottomTypeDetails + extends BottomTypeDetails { inferenceRules: Array>; } @@ -22,36 +27,47 @@ export interface BottomKindOptions { name: string; } -export const BottomKindName = 'BottomKind'; +export const BottomKindName = "BottomKind"; export interface BottomFactoryService { - create(typeDetails: BottomTypeDetails): BottomConfigurationChain; + create( + typeDetails: BottomTypeDetails, + ): BottomConfigurationChain; get(typeDetails: BottomTypeDetails): BottomType | undefined; } interface BottomConfigurationChain { - inferenceRule(rule: InferCurrentTypeRule): BottomConfigurationChain; + inferenceRule( + rule: InferCurrentTypeRule, + ): BottomConfigurationChain; finish(): BottomType; } -export class BottomKind implements Kind, BottomFactoryService { - readonly $name: 'BottomKind'; +export class BottomKind + implements Kind, BottomFactoryService +{ + readonly $name: "BottomKind"; readonly services: TypirServices; readonly options: Readonly; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = BottomKindName; this.services = services; this.services.infrastructure.Kinds.register(this); this.options = this.collectOptions(options); } - protected collectOptions(options?: Partial): BottomKindOptions { + protected collectOptions( + options?: Partial, + ): BottomKindOptions { return { // the default values: - name: 'never', + name: "never", // the actually overriden values: - ...options + ...options, }; } @@ -60,28 +76,40 @@ export class BottomKind implements Kind, BottomFactoryService): BottomConfigurationChain { + create( + typeDetails: BottomTypeDetails, + ): BottomConfigurationChain { assertTrue(this.get(typeDetails) === undefined); - return new BottomConfigurationChainImpl(this.services, this, typeDetails); + return new BottomConfigurationChainImpl( + this.services, + this, + typeDetails, + ); } calculateIdentifier(_typeDetails: BottomTypeDetails): string { return this.options.name; } - } -export function isBottomKind(kind: unknown): kind is BottomKind { +export function isBottomKind( + kind: unknown, +): kind is BottomKind { return isKind(kind) && kind.$name === BottomKindName; } - -class BottomConfigurationChainImpl implements BottomConfigurationChain { +class BottomConfigurationChainImpl + implements BottomConfigurationChain +{ protected readonly services: TypirServices; protected readonly kind: BottomKind; protected readonly typeDetails: CreateBottomTypeDetails; - constructor(services: TypirServices, kind: BottomKind, typeDetails: BottomTypeDetails) { + constructor( + services: TypirServices, + kind: BottomKind, + typeDetails: BottomTypeDetails, + ) { this.services = services; this.kind = kind; this.typeDetails = { @@ -90,16 +118,28 @@ class BottomConfigurationChainImpl implements BottomConfigurationC }; } - inferenceRule(rule: InferCurrentTypeRule): BottomConfigurationChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferCurrentTypeRule); + inferenceRule( + rule: InferCurrentTypeRule, + ): BottomConfigurationChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferCurrentTypeRule, + ); return this; } finish(): BottomType { - const bottomType = new BottomType(this.kind as BottomKind, this.kind.calculateIdentifier(this.typeDetails), this.typeDetails); + const bottomType = new BottomType( + this.kind as BottomKind, + this.kind.calculateIdentifier(this.typeDetails), + this.typeDetails, + ); this.services.infrastructure.Graph.addNode(bottomType); - registerInferCurrentTypeRules(this.typeDetails.inferenceRules, bottomType, this.services); + registerInferCurrentTypeRules( + this.typeDetails.inferenceRules, + bottomType, + this.services, + ); return bottomType; } diff --git a/packages/typir/src/kinds/bottom/bottom-type.ts b/packages/typir/src/kinds/bottom/bottom-type.ts index d28fc476..019e9eab 100644 --- a/packages/typir/src/kinds/bottom/bottom-type.ts +++ b/packages/typir/src/kinds/bottom/bottom-type.ts @@ -4,24 +4,28 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { createKindConflict } from '../../utils/utils-type-comparison.js'; -import { BottomKind, BottomTypeDetails, isBottomKind } from './bottom-kind.js'; +import { TypeGraphListener } from "../../graph/type-graph.js"; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { createKindConflict } from "../../utils/utils-type-comparison.js"; +import { BottomKind, BottomTypeDetails, isBottomKind } from "./bottom-kind.js"; export class BottomType extends Type implements TypeGraphListener { override readonly kind: BottomKind; - constructor(kind: BottomKind, identifier: string, typeDetails: BottomTypeDetails) { + constructor( + kind: BottomKind, + identifier: string, + typeDetails: BottomTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; this.defineTheInitializationProcessOfThisType({}); // no preconditions // ensure, that this Bottom type is a sub-type of all (other) types: const graph = kind.services.infrastructure.Graph; - graph.getAllRegisteredTypes().forEach(t => this.markAsSubType(t)); // the already existing types + graph.getAllRegisteredTypes().forEach((t) => this.markAsSubType(t)); // the already existing types graph.addListener(this); // all upcomping types } @@ -31,7 +35,9 @@ export class BottomType extends Type implements TypeGraphListener { protected markAsSubType(type: Type): void { if (type !== this) { - this.kind.services.Subtype.markAsSubType(this, type, { checkForCycles: false }); + this.kind.services.Subtype.markAsSubType(this, type, { + checkForCycles: false, + }); } } @@ -51,15 +57,16 @@ export class BottomType extends Type implements TypeGraphListener { if (isBottomType(otherType)) { return []; } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(this, otherType)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(this, otherType)], + }, + ]; } } - } export function isBottomType(type: unknown): type is BottomType { diff --git a/packages/typir/src/kinds/class/class-initializer.ts b/packages/typir/src/kinds/class/class-initializer.ts index e264ecce..dfa886b0 100644 --- a/packages/typir/src/kinds/class/class-initializer.ts +++ b/packages/typir/src/kinds/class/class-initializer.ts @@ -4,39 +4,85 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type, TypeStateListener } from '../../graph/type-node.js'; -import { TypeInitializer } from '../../initialization/type-initializer.js'; -import { InferenceProblem, InferenceRuleNotApplicable, TypeInferenceRule } from '../../services/inference.js'; -import { TypirServices } from '../../typir.js'; -import { bindInferCurrentTypeRule, bindValidateCurrentTypeRule, InferenceRuleWithOptions, optionsBoundToType, ValidationRuleWithOptions } from '../../utils/utils-definitions.js'; -import { checkNameTypesMap, createTypeCheckStrategy, MapListConverter } from '../../utils/utils-type-comparison.js'; -import { assertTypirType, toArray } from '../../utils/utils.js'; -import { ClassKind, CreateClassTypeDetails, InferClassLiteral } from './class-kind.js'; -import { ClassType, isClassType } from './class-type.js'; +import { isType, Type, TypeStateListener } from "../../graph/type-node.js"; +import { TypeInitializer } from "../../initialization/type-initializer.js"; +import { + InferenceProblem, + InferenceRuleNotApplicable, + TypeInferenceRule, +} from "../../services/inference.js"; +import { TypirServices } from "../../typir.js"; +import { + bindInferCurrentTypeRule, + bindValidateCurrentTypeRule, + InferenceRuleWithOptions, + optionsBoundToType, + ValidationRuleWithOptions, +} from "../../utils/utils-definitions.js"; +import { + checkNameTypesMap, + createTypeCheckStrategy, + MapListConverter, +} from "../../utils/utils-type-comparison.js"; +import { assertTypirType, toArray } from "../../utils/utils.js"; +import { + ClassKind, + CreateClassTypeDetails, + InferClassLiteral, +} from "./class-kind.js"; +import { ClassType, isClassType } from "./class-type.js"; -export class ClassTypeInitializer extends TypeInitializer implements TypeStateListener { +export class ClassTypeInitializer + extends TypeInitializer + implements TypeStateListener +{ protected readonly typeDetails: CreateClassTypeDetails; protected readonly kind: ClassKind; - protected inferenceRules: Array> = []; - protected validationRules: Array> = []; + protected inferenceRules: Array> = + []; + protected validationRules: Array> = + []; protected initialClassType: ClassType; - constructor(services: TypirServices, kind: ClassKind, typeDetails: CreateClassTypeDetails) { + constructor( + services: TypirServices, + kind: ClassKind, + typeDetails: CreateClassTypeDetails, + ) { super(services); this.typeDetails = typeDetails; this.kind = kind; // create the class type - this.initialClassType = new ClassType(kind as ClassKind, typeDetails as CreateClassTypeDetails); - if (kind.options.typing === 'Structural') { + this.initialClassType = new ClassType( + kind as ClassKind, + typeDetails as CreateClassTypeDetails, + ); + if (kind.options.typing === "Structural") { // register structural classes also by their names, since these names are usually used for reference in the DSL/AST! - this.services.infrastructure.Graph.addNode(this.initialClassType, kind.calculateIdentifierWithClassNameOnly(typeDetails)); + this.services.infrastructure.Graph.addNode( + this.initialClassType, + kind.calculateIdentifierWithClassNameOnly(typeDetails), + ); } - this.createInferenceAndValidationRules(this.typeDetails, this.initialClassType); + this.createInferenceAndValidationRules( + this.typeDetails, + this.initialClassType, + ); // register all the inference rules already now to enable early type inference for this Class type ('undefined', since its Identifier is still missing) - this.inferenceRules.forEach(rule => services.Inference.addInferenceRule(rule.rule, optionsBoundToType(rule.options, undefined))); - this.validationRules.forEach(rule => services.validation.Collector.addValidationRule(rule.rule, optionsBoundToType(rule.options, undefined))); + this.inferenceRules.forEach((rule) => + services.Inference.addInferenceRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); + this.validationRules.forEach((rule) => + services.validation.Collector.addValidationRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); this.initialClassType.addListener(this, true); // trigger directly, if some initialization states are already reached! } @@ -55,31 +101,83 @@ export class ClassTypeInitializer extends TypeInitializer skip the classType! classType.removeListener(this); // since this ClassTypeInitializer initialized the invalid type, there is nothing to do anymore here! - if (this.kind.options.typing === 'Structural') { + if (this.kind.options.typing === "Structural") { // replace the type in the type graph - const nameBasedIdentifier = this.kind.calculateIdentifierWithClassNameOnly(this.typeDetails); - this.services.infrastructure.Graph.removeNode(classType, nameBasedIdentifier); - this.services.infrastructure.Graph.addNode(readyClassType, nameBasedIdentifier); + const nameBasedIdentifier = + this.kind.calculateIdentifierWithClassNameOnly( + this.typeDetails, + ); + this.services.infrastructure.Graph.removeNode( + classType, + nameBasedIdentifier, + ); + this.services.infrastructure.Graph.addNode( + readyClassType, + nameBasedIdentifier, + ); } // remove the inference rules for the invalid type - this.inferenceRules.forEach(rule => this.services.Inference.removeInferenceRule(rule.rule, optionsBoundToType(rule.options, undefined))); - this.validationRules.forEach(rule => this.services.validation.Collector.removeValidationRule(rule.rule, optionsBoundToType(rule.options, undefined))); + this.inferenceRules.forEach((rule) => + this.services.Inference.removeInferenceRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); + this.validationRules.forEach((rule) => + this.services.validation.Collector.removeValidationRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); // but re-create the inference rules for the new type!! // This is required, since inference rules for different declarations in the AST might be different, but should infer the same Typir type! - this.createInferenceAndValidationRules(this.typeDetails, readyClassType); + this.createInferenceAndValidationRules( + this.typeDetails, + readyClassType, + ); // add the new rules - this.inferenceRules.forEach(rule => this.services.Inference.addInferenceRule(rule.rule, optionsBoundToType(rule.options, readyClassType))); - this.validationRules.forEach(rule => this.services.validation.Collector.addValidationRule(rule.rule, optionsBoundToType(rule.options, readyClassType))); + this.inferenceRules.forEach((rule) => + this.services.Inference.addInferenceRule( + rule.rule, + optionsBoundToType(rule.options, readyClassType), + ), + ); + this.validationRules.forEach((rule) => + this.services.validation.Collector.addValidationRule( + rule.rule, + optionsBoundToType(rule.options, readyClassType), + ), + ); } else { // the class type is unchanged (this is the usual case) // keep the existing inference rules, but register it for the unchanged class type - this.inferenceRules.forEach(rule => this.services.Inference.removeInferenceRule(rule.rule, optionsBoundToType(rule.options, undefined))); - this.validationRules.forEach(rule => this.services.validation.Collector.removeValidationRule(rule.rule, optionsBoundToType(rule.options, undefined))); + this.inferenceRules.forEach((rule) => + this.services.Inference.removeInferenceRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); + this.validationRules.forEach((rule) => + this.services.validation.Collector.removeValidationRule( + rule.rule, + optionsBoundToType(rule.options, undefined), + ), + ); - this.inferenceRules.forEach(rule => this.services.Inference.addInferenceRule(rule.rule, optionsBoundToType(rule.options, readyClassType))); - this.validationRules.forEach(rule => this.services.validation.Collector.addValidationRule(rule.rule, optionsBoundToType(rule.options, readyClassType))); + this.inferenceRules.forEach((rule) => + this.services.Inference.addInferenceRule( + rule.rule, + optionsBoundToType(rule.options, readyClassType), + ), + ); + this.validationRules.forEach((rule) => + this.services.validation.Collector.addValidationRule( + rule.rule, + optionsBoundToType(rule.options, readyClassType), + ), + ); } } @@ -89,7 +187,9 @@ export class ClassTypeInitializer extends TypeInitializer extends TypeInitializer, classType: ClassType): void { + protected createInferenceAndValidationRules( + typeDetails: CreateClassTypeDetails, + classType: ClassType, + ): void { // clear the current list ... this.inferenceRules.splice(0, this.inferenceRules.length); this.validationRules.splice(0, this.validationRules.length); // ... and recreate all rules for (const inferenceRulesForClassDeclaration of typeDetails.inferenceRulesForClassDeclaration) { - this.inferenceRules.push(bindInferCurrentTypeRule(inferenceRulesForClassDeclaration, classType)); + this.inferenceRules.push( + bindInferCurrentTypeRule( + inferenceRulesForClassDeclaration, + classType, + ), + ); // TODO check values for fields for structual typing! - const validationRule = bindValidateCurrentTypeRule(inferenceRulesForClassDeclaration, classType); + const validationRule = bindValidateCurrentTypeRule< + ClassType, + LanguageType + >(inferenceRulesForClassDeclaration, classType); if (validationRule) { this.validationRules.push(validationRule); } } for (const inferenceRulesForClassLiterals of typeDetails.inferenceRulesForClassLiterals) { - this.inferenceRules.push(this.createInferenceRuleForLiteral(inferenceRulesForClassLiterals, classType)); - const validationRule = this.createValidationRuleForLiteral(inferenceRulesForClassLiterals, classType); + this.inferenceRules.push( + this.createInferenceRuleForLiteral( + inferenceRulesForClassLiterals, + classType, + ), + ); + const validationRule = this.createValidationRuleForLiteral( + inferenceRulesForClassLiterals, + classType, + ); if (validationRule) { this.validationRules.push(validationRule); } @@ -129,16 +248,27 @@ export class ClassTypeInitializer extends TypeInitializer { - if (inferenceRulesForFieldAccess.filter !== undefined && inferenceRulesForFieldAccess.filter(languageNode) === false) { + if ( + inferenceRulesForFieldAccess.filter !== undefined && + inferenceRulesForFieldAccess.filter(languageNode) === + false + ) { return InferenceRuleNotApplicable; } - if (inferenceRulesForFieldAccess.matching !== undefined && inferenceRulesForFieldAccess.matching(languageNode, classType) === false) { + if ( + inferenceRulesForFieldAccess.matching !== undefined && + inferenceRulesForFieldAccess.matching( + languageNode, + classType, + ) === false + ) { return InferenceRuleNotApplicable; } - const result = inferenceRulesForFieldAccess.field(languageNode); + const result = + inferenceRulesForFieldAccess.field(languageNode); if (result === InferenceRuleNotApplicable) { return InferenceRuleNotApplicable; - } else if (typeof result === 'string') { + } else if (typeof result === "string") { // get the type of the given field name const fieldType = classType.getFields(true).get(result); if (fieldType) { @@ -161,28 +291,46 @@ export class ClassTypeInitializer extends TypeInitializer= 1) { this.validationRules.push({ rule: (languageNode, accept, typir) => { - if (inferenceRulesForFieldAccess.filter !== undefined && inferenceRulesForFieldAccess.filter(languageNode) === false) { + if ( + inferenceRulesForFieldAccess.filter !== undefined && + inferenceRulesForFieldAccess.filter( + languageNode, + ) === false + ) { return; } - if (inferenceRulesForFieldAccess.matching !== undefined && inferenceRulesForFieldAccess.matching(languageNode, classType) === false) { + if ( + inferenceRulesForFieldAccess.matching !== + undefined && + inferenceRulesForFieldAccess.matching( + languageNode, + classType, + ) === false + ) { return; } - const field = inferenceRulesForFieldAccess.field(languageNode); + const field = + inferenceRulesForFieldAccess.field(languageNode); if (field === InferenceRuleNotApplicable) { return; } - const fieldType = typeof field === 'string' - ? classType.getFields(true).get(field) - : typir.Inference.inferType(field); + const fieldType = + typeof field === "string" + ? classType.getFields(true).get(field) + : typir.Inference.inferType(field); if (isType(fieldType) === false) { return; } // TODO review: insert 'fieldType' as additional parameter? - validationRules.forEach(rule => rule(languageNode, classType, accept, typir)); + validationRules.forEach((rule) => + rule(languageNode, classType, accept, typir), + ); }, options: { languageKey: inferenceRulesForFieldAccess.languageKey, @@ -193,17 +341,24 @@ export class ClassTypeInitializer extends TypeInitializer(rule: InferClassLiteral, classType: ClassType): InferenceRuleWithOptions { + protected createInferenceRuleForLiteral( + rule: InferClassLiteral, + classType: ClassType, + ): InferenceRuleWithOptions { const mapListConverter = new MapListConverter(); const kind = this.kind; return { rule: { inferTypeWithoutChildren(languageNode, _typir) { - const result = rule.filter === undefined || rule.filter(languageNode); + const result = + rule.filter === undefined || rule.filter(languageNode); if (result) { - const matching = rule.matching === undefined || rule.matching(languageNode, classType); + const matching = + rule.matching === undefined || + rule.matching(languageNode, classType); if (matching) { - const inputArguments = rule.inputValuesForFields(languageNode); + const inputArguments = + rule.inputValuesForFields(languageNode); if (inputArguments.size >= 1) { return mapListConverter.toList(inputArguments); } else { @@ -220,13 +375,20 @@ export class ClassTypeInitializer extends TypeInitializer= 1) { // (only) for overloaded functions, the types of the parameters need to be inferred in order to determine an exact match @@ -234,7 +396,7 @@ export class ClassTypeInitializer extends TypeInitializer, subProblems: checkedFieldsProblems, }; @@ -250,17 +412,26 @@ export class ClassTypeInitializer extends TypeInitializer(rule: InferClassLiteral, classType: ClassType): ValidationRuleWithOptions | undefined { + protected createValidationRuleForLiteral( + rule: InferClassLiteral, + classType: ClassType, + ): ValidationRuleWithOptions | undefined { const validationRules = toArray(rule.validation); if (validationRules.length <= 0) { return undefined; } return { rule: (languageNode, accept, typir) => { - if (rule.filter !== undefined && rule.filter(languageNode) === false) { + if ( + rule.filter !== undefined && + rule.filter(languageNode) === false + ) { return; } - if (rule.matching !== undefined && rule.matching(languageNode, classType) === false) { + if ( + rule.matching !== undefined && + rule.matching(languageNode, classType) === false + ) { return; } const inputArguments = rule.inputValuesForFields(languageNode); @@ -270,17 +441,30 @@ export class ClassTypeInitializer extends TypeInitializer skip validations } - const expectedFieldType = allExpectedFields.get(fieldName); + const expectedFieldType = + allExpectedFields.get(fieldName); if (expectedFieldType === undefined) { return; // argument for a non-existing parameter } - if (compareFieldTypes(actualFieldType, expectedFieldType) !== undefined) { + if ( + compareFieldTypes( + actualFieldType, + expectedFieldType, + ) !== undefined + ) { return; // types are different } // everything is fine with this argument @@ -289,7 +473,9 @@ export class ClassTypeInitializer extends TypeInitializer rule(languageNode, classType, accept, typir)); + validationRules.forEach((rule) => + rule(languageNode, classType, accept, typir), + ); }, options: { languageKey: rule.languageKey, @@ -297,5 +483,4 @@ export class ClassTypeInitializer extends TypeInitializer { name: string; @@ -42,15 +55,21 @@ export interface CreateMethodDetails { type: TypeSelector; } -export interface ClassTypeDetails extends TypeDetails { +export interface ClassTypeDetails + extends TypeDetails { className: string; - superClasses?: TypeSelector | Array>; + superClasses?: + | TypeSelector + | Array>; fields: Array>; methods: Array>; } -export interface CreateClassTypeDetails extends ClassTypeDetails { +export interface CreateClassTypeDetails + extends ClassTypeDetails { // inference rules for the Class - inferenceRulesForClassDeclaration: Array>; + inferenceRulesForClassDeclaration: Array< + InferCurrentTypeRule + >; inferenceRulesForClassLiterals: Array>; // e.g. Constructor calls, References // inference rules for its Fields inferenceRulesForFieldAccess: Array>; @@ -60,39 +79,64 @@ export interface CreateClassTypeDetails extends ClassTypeDetails extends InferCurrentTypeRule { +export interface InferClassLiteral< + LanguageType, + T extends LanguageType = LanguageType, +> extends InferCurrentTypeRule { inputValuesForFields: (languageNode: T) => Map; // simple field name (including inherited fields) => value for this field! } -export interface InferClassFieldAccess extends InferCurrentTypeRule { - field: (languageNode: T) => string | LanguageType | InferenceRuleNotApplicable; // name of the field | language node to infer the type of the field (e.g. the type) | rule not applicable +export interface InferClassFieldAccess< + LanguageType, + T extends LanguageType = LanguageType, +> extends InferCurrentTypeRule { + field: ( + languageNode: T, + ) => string | LanguageType | InferenceRuleNotApplicable; // name of the field | language node to infer the type of the field (e.g. the type) | rule not applicable } export interface ClassFactoryService { - create(typeDetails: ClassTypeDetails): ClassConfigurationChain; - get(typeDetails: ClassTypeDetails | string): TypeReference; + create( + typeDetails: ClassTypeDetails, + ): ClassConfigurationChain; + get( + typeDetails: ClassTypeDetails | string, + ): TypeReference; // some predefined valitions: - createUniqueClassValidation(options: RegistrationOptions): UniqueClassValidation; + createUniqueClassValidation( + options: RegistrationOptions, + ): UniqueClassValidation; - createUniqueMethodValidation(options: UniqueMethodValidationOptions & RegistrationOptions): ValidationRule; + createUniqueMethodValidation( + options: UniqueMethodValidationOptions & + RegistrationOptions, + ): ValidationRule; - createNoSuperClassCyclesValidation(options: NoSuperClassCyclesValidationOptions & RegistrationOptions): ValidationRule; + createNoSuperClassCyclesValidation( + options: NoSuperClassCyclesValidationOptions & + RegistrationOptions, + ): ValidationRule; // benefits of this design decision: the returned rule is easier to exchange, users can use the known factory API with auto-completion (no need to remember the names of the validations) } export interface ClassConfigurationChain { - inferenceRuleForClassDeclaration(rule: InferCurrentTypeRule): ClassConfigurationChain; - inferenceRuleForClassLiterals(rule: InferClassLiteral): ClassConfigurationChain; + inferenceRuleForClassDeclaration( + rule: InferCurrentTypeRule, + ): ClassConfigurationChain; + inferenceRuleForClassLiterals( + rule: InferClassLiteral, + ): ClassConfigurationChain; - inferenceRuleForFieldAccess(rule: InferClassFieldAccess): ClassConfigurationChain; + inferenceRuleForFieldAccess( + rule: InferClassFieldAccess, + ): ClassConfigurationChain; finish(): TypeInitializer; } - /** * Classes have a name and have an arbitrary number of fields, consisting of a name and a type, and an arbitrary number of super-classes. * Fields have exactly one type and no multiplicity (which can be realized with a type of kind 'MultiplicityKind'). @@ -100,12 +144,17 @@ export interface ClassConfigurationChain { * The field name is used to identify fields of classes. * The order of fields is not defined, i.e. there is no order of fields. */ -export class ClassKind implements Kind, ClassFactoryService { - readonly $name: 'ClassKind'; +export class ClassKind + implements Kind, ClassFactoryService +{ + readonly $name: "ClassKind"; readonly services: TypirServices; readonly options: Readonly; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = ClassKindName; this.services = services; this.services.infrastructure.Kinds.register(this); @@ -113,15 +162,17 @@ export class ClassKind implements Kind, ClassFactoryService= 0); // no negative values } - protected collectOptions(options?: Partial): ClassKindOptions { + protected collectOptions( + options?: Partial, + ): ClassKindOptions { return { // the default values: - typing: 'Nominal', + typing: "Nominal", maximumNumberOfSuperClasses: 1, - subtypeFieldChecking: 'EQUAL_TYPE', - identifierPrefix: 'class', + subtypeFieldChecking: "EQUAL_TYPE", + identifierPrefix: "class", // the actually overriden values: - ...options + ...options, }; } @@ -130,13 +181,22 @@ export class ClassKind implements Kind, ClassFactoryService | string): TypeReference { // string for nominal typing - if (typeof typeDetails === 'string') { + get( + typeDetails: ClassTypeDetails | string, + ): TypeReference { + // string for nominal typing + if (typeof typeDetails === "string") { // nominal typing - return new TypeReference(typeDetails, this.services); + return new TypeReference( + typeDetails, + this.services, + ); } else { // structural typing (does this case occur in practise?) - return new TypeReference(() => this.calculateIdentifier(typeDetails), this.services); + return new TypeReference( + () => this.calculateIdentifier(typeDetails), + this.services, + ); } } @@ -147,12 +207,20 @@ export class ClassKind implements Kind, ClassFactoryService): ClassConfigurationChain { - return new ClassConfigurationChainImpl(this.services, this, typeDetails); + create( + typeDetails: ClassTypeDetails, + ): ClassConfigurationChain { + return new ClassConfigurationChainImpl( + this.services, + this, + typeDetails, + ); } protected getIdentifierPrefix(): string { - return this.options.identifierPrefix ? (this.options.identifierPrefix + '-') : ''; + return this.options.identifierPrefix + ? this.options.identifierPrefix + "-" + : ""; } /** @@ -170,29 +238,39 @@ export class ClassKind implements Kind, ClassFactoryService): string { // purpose of identifier: distinguish different types; NOT: not uniquely overloaded types - if (this.options.typing === 'Structural') { + if (this.options.typing === "Structural") { // fields const fields: string = typeDetails.fields - .map(f => `${f.name}:${this.services.infrastructure.TypeResolver.resolve(f.type).getIdentifier()}`) // the names and the types of the fields are relevant, since different field types lead to different class types! + .map( + (f) => + `${f.name}:${this.services.infrastructure.TypeResolver.resolve(f.type).getIdentifier()}`, + ) // the names and the types of the fields are relevant, since different field types lead to different class types! .sort() // the order of fields does not matter, therefore we need a stable order to make the identifiers comparable - .join(','); + .join(","); // methods const methods: string = typeDetails.methods - .map(m => this.services.infrastructure.TypeResolver.resolve(m.type).getIdentifier()) + .map((m) => + this.services.infrastructure.TypeResolver.resolve( + m.type, + ).getIdentifier(), + ) .sort() // the order of methods does not matter, therefore we need a stable order to make the identifiers comparable - .join(','); + .join(","); // super classes (TODO oder strukturell per getAllSuperClassX lösen?!) const superClasses: string = toArray(typeDetails.superClasses) - .map(selector => { - const type = this.services.infrastructure.TypeResolver.resolve(selector); + .map((selector) => { + const type = + this.services.infrastructure.TypeResolver.resolve( + selector, + ); assertTypirType(type, isClassType); return type.getIdentifier(); }) .sort() - .join(','); + .join(","); // complete identifier (the name of the class does not matter for structural typing!) return `${this.getIdentifierPrefix()}fields{${fields}}-methods{${methods}}-extends{${superClasses}}`; - } else if (this.options.typing === 'Nominal') { + } else if (this.options.typing === "Nominal") { // only the name of the class matters for nominal typing! return this.calculateIdentifierWithClassNameOnly(typeDetails); } else { @@ -207,59 +285,89 @@ export class ClassKind implements Kind, ClassFactoryService): string { + calculateIdentifierWithClassNameOnly( + typeDetails: ClassTypeDetails, + ): string { return `${this.getIdentifierPrefix()}${typeDetails.className}`; } - getTopClassKind(): TopClassKind { // ensure, that Typir uses the predefined 'TopClass' kind const kind = this.services.infrastructure.Kinds.get(TopClassKindName); - return isTopClassKind(kind) ? kind : new TopClassKind(this.services); + return isTopClassKind(kind) + ? kind + : new TopClassKind(this.services); } - createUniqueClassValidation(options: RegistrationOptions): UniqueClassValidation { + createUniqueClassValidation( + options: RegistrationOptions, + ): UniqueClassValidation { const rule = new UniqueClassValidation(this.services); - if (options.registration === 'MYSELF') { + if (options.registration === "MYSELF") { // do nothing, the user is responsible to register the rule } else { - this.services.validation.Collector.addValidationRule(rule, options.registration); + this.services.validation.Collector.addValidationRule( + rule, + options.registration, + ); } return rule; } - createUniqueMethodValidation(options: UniqueMethodValidationOptions & RegistrationOptions): ValidationRule { - const rule = new UniqueMethodValidation(this.services, options); - if (options.registration === 'MYSELF') { + createUniqueMethodValidation( + options: UniqueMethodValidationOptions & + RegistrationOptions, + ): ValidationRule { + const rule = new UniqueMethodValidation( + this.services, + options, + ); + if (options.registration === "MYSELF") { // do nothing, the user is responsible to register the rule } else { - this.services.validation.Collector.addValidationRule(rule, options.registration); + this.services.validation.Collector.addValidationRule( + rule, + options.registration, + ); } return rule; } - createNoSuperClassCyclesValidation(options: NoSuperClassCyclesValidationOptions & RegistrationOptions): ValidationRule { + createNoSuperClassCyclesValidation( + options: NoSuperClassCyclesValidationOptions & + RegistrationOptions, + ): ValidationRule { const rule = createNoSuperClassCyclesValidation(options); - if (options.registration === 'MYSELF') { + if (options.registration === "MYSELF") { // do nothing, the user is responsible to register the rule } else { - this.services.validation.Collector.addValidationRule(rule, options.registration); + this.services.validation.Collector.addValidationRule( + rule, + options.registration, + ); } return rule; } } -export function isClassKind(kind: unknown): kind is ClassKind { +export function isClassKind( + kind: unknown, +): kind is ClassKind { return isKind(kind) && kind.$name === ClassKindName; } - -class ClassConfigurationChainImpl implements ClassConfigurationChain { +class ClassConfigurationChainImpl + implements ClassConfigurationChain +{ protected readonly services: TypirServices; protected readonly kind: ClassKind; protected readonly typeDetails: CreateClassTypeDetails; - constructor(services: TypirServices, kind: ClassKind, typeDetails: ClassTypeDetails) { + constructor( + services: TypirServices, + kind: ClassKind, + typeDetails: ClassTypeDetails, + ) { this.services = services; this.kind = kind; this.typeDetails = { @@ -270,22 +378,38 @@ class ClassConfigurationChainImpl implements ClassConfigurationCha }; } - inferenceRuleForClassDeclaration(rule: InferCurrentTypeRule): ClassConfigurationChain { - this.typeDetails.inferenceRulesForClassDeclaration.push(rule as unknown as InferCurrentTypeRule); + inferenceRuleForClassDeclaration( + rule: InferCurrentTypeRule, + ): ClassConfigurationChain { + this.typeDetails.inferenceRulesForClassDeclaration.push( + rule as unknown as InferCurrentTypeRule, + ); return this; } - inferenceRuleForClassLiterals(rule: InferClassLiteral): ClassConfigurationChain { - this.typeDetails.inferenceRulesForClassLiterals.push(rule as unknown as InferClassLiteral); + inferenceRuleForClassLiterals( + rule: InferClassLiteral, + ): ClassConfigurationChain { + this.typeDetails.inferenceRulesForClassLiterals.push( + rule as unknown as InferClassLiteral, + ); return this; } - inferenceRuleForFieldAccess(rule: InferClassFieldAccess): ClassConfigurationChain { - this.typeDetails.inferenceRulesForFieldAccess.push(rule as unknown as InferClassFieldAccess); + inferenceRuleForFieldAccess( + rule: InferClassFieldAccess, + ): ClassConfigurationChain { + this.typeDetails.inferenceRulesForFieldAccess.push( + rule as unknown as InferClassFieldAccess, + ); return this; } finish(): TypeInitializer { - return new ClassTypeInitializer(this.services, this.kind, this.typeDetails); + return new ClassTypeInitializer( + this.services, + this.kind, + this.typeDetails, + ); } } diff --git a/packages/typir/src/kinds/class/class-type.ts b/packages/typir/src/kinds/class/class-type.ts index ff210a93..8907f58f 100644 --- a/packages/typir/src/kinds/class/class-type.ts +++ b/packages/typir/src/kinds/class/class-type.ts @@ -4,14 +4,24 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from '../../graph/type-node.js'; -import { TypeReference } from '../../initialization/type-reference.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { checkNameTypesMap, checkValueForConflict, createKindConflict, createTypeCheckStrategy, IndexedTypeConflict } from '../../utils/utils-type-comparison.js'; -import { assertUnreachable, removeFromArray, toArray } from '../../utils/utils.js'; -import { FunctionType } from '../function/function-type.js'; -import { ClassKind, ClassTypeDetails, isClassKind } from './class-kind.js'; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeReference } from "../../initialization/type-reference.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { + checkNameTypesMap, + checkValueForConflict, + createKindConflict, + createTypeCheckStrategy, + IndexedTypeConflict, +} from "../../utils/utils-type-comparison.js"; +import { + assertUnreachable, + removeFromArray, + toArray, +} from "../../utils/utils.js"; +import { FunctionType } from "../function/function-type.js"; +import { ClassKind, ClassTypeDetails, isClassKind } from "./class-kind.js"; export interface FieldDetails { name: string; @@ -38,11 +48,16 @@ export class ClassType extends Type { protected readonly fields: Map = new Map(); // unordered protected methods: MethodDetails[]; // unordered - constructor(kind: ClassKind, typeDetails: ClassTypeDetails) { - super(kind.options.typing === 'Nominal' - ? kind.calculateIdentifierWithClassNameOnly(typeDetails) // use the name of the class as identifier already now - : undefined, // the identifier for structurally typed classes will be set later after resolving all fields and methods - typeDetails); + constructor( + kind: ClassKind, + typeDetails: ClassTypeDetails, + ) { + super( + kind.options.typing === "Nominal" + ? kind.calculateIdentifierWithClassNameOnly(typeDetails) // use the name of the class as identifier already now + : undefined, // the identifier for structurally typed classes will be set later after resolving all fields and methods + typeDetails, + ); this.kind = kind; this.className = typeDetails.className; @@ -50,65 +65,92 @@ export class ClassType extends Type { const thisType = this; // resolve the super classes - this.superClasses = toArray(typeDetails.superClasses).map(superr => { - const superRef = new TypeReference(superr, kind.services); - superRef.addListener({ - onTypeReferenceResolved(_reference, superType) { - // after the super-class is complete ... - superType.subClasses.push(thisType); // register this class as sub-class for that super-class - kind.services.Subtype.markAsSubType(thisType, superType, // register the sub-type relationship in the type graph - { checkForCycles: false }); // ignore cycles in sub-super-class relationships for now, since they are reported with a dedicated validation for the user + this.superClasses = toArray(typeDetails.superClasses).map((superr) => { + const superRef = new TypeReference( + superr, + kind.services, + ); + superRef.addListener( + { + onTypeReferenceResolved(_reference, superType) { + // after the super-class is complete ... + superType.subClasses.push(thisType); // register this class as sub-class for that super-class + kind.services.Subtype.markAsSubType( + thisType, + superType, // register the sub-type relationship in the type graph + { checkForCycles: false }, + ); // ignore cycles in sub-super-class relationships for now, since they are reported with a dedicated validation for the user + }, + onTypeReferenceInvalidated(_reference, superType) { + if (superType) { + // if the superType gets invalid ... + removeFromArray(thisType, superType.subClasses); // de-register this class as sub-class of the super-class + // TODO unmark sub-type relationship (or already done automatically, since the type is removed from the graph?? gibt es noch andere Möglichkeiten eine Reference zu invalidieren außer dass der Type entfernt wurde??) + } else { + // initially do nothing + } + }, }, - onTypeReferenceInvalidated(_reference, superType) { - if (superType) { - // if the superType gets invalid ... - removeFromArray(thisType, superType.subClasses); // de-register this class as sub-class of the super-class - // TODO unmark sub-type relationship (or already done automatically, since the type is removed from the graph?? gibt es noch andere Möglichkeiten eine Reference zu invalidieren außer dass der Type entfernt wurde??) - } else { - // initially do nothing - } - }, - }, true); + true, + ); return superRef; }); // resolve fields typeDetails.fields - .map(field => { - name: field.name, - type: new TypeReference(field.type, kind.services), - }) - .forEach(field => { + .map( + (field) => + { + name: field.name, + type: new TypeReference(field.type, kind.services), + }, + ) + .forEach((field) => { if (this.fields.has(field.name)) { // check collisions of field names - throw new Error(`The field name '${field.name}' is not unique for class '${this.className}'.`); + throw new Error( + `The field name '${field.name}' is not unique for class '${this.className}'.`, + ); } else { this.fields.set(field.name, field); } }); const refFields: Array> = []; - [...this.fields.values()].forEach(f => refFields.push(f.type)); + [...this.fields.values()].forEach((f) => refFields.push(f.type)); // resolve methods - this.methods = typeDetails.methods.map(method => { - type: new TypeReference(method.type, kind.services), - }); - const refMethods = this.methods.map(m => m.type); + this.methods = typeDetails.methods.map( + (method) => + { + type: new TypeReference( + method.type, + kind.services, + ), + }, + ); + const refMethods = this.methods.map((m) => m.type); // the uniqueness of methods can be checked with the predefined UniqueMethodValidation below // const all: Array> = []; const fieldsAndMethods: Array> = []; fieldsAndMethods.push(...refFields); - fieldsAndMethods.push(...(refMethods as unknown as Array>)); + fieldsAndMethods.push( + ...(refMethods as unknown as Array>), + ); this.defineTheInitializationProcessOfThisType({ preconditionsForIdentifiable: { referencesToBeIdentifiable: fieldsAndMethods, }, preconditionsForCompleted: { - referencesToBeCompleted: this.superClasses as unknown as Array>, + referencesToBeCompleted: this.superClasses as unknown as Array< + TypeReference + >, }, - referencesRelevantForInvalidation: [...fieldsAndMethods, ...(this.superClasses as unknown as Array>)], + referencesRelevantForInvalidation: [ + ...fieldsAndMethods, + ...(this.superClasses as unknown as Array>), + ], onIdentifiable: () => { // the identifier is calculated now this.identifier = this.kind.calculateIdentifier(typeDetails); // TODO it is still not nice, that the type resolving is done again, since the TypeReferences here are not reused @@ -118,8 +160,13 @@ export class ClassType extends Type { // when all super classes are completely available, do the following checks: // check number of allowed super classes if (this.kind.options.maximumNumberOfSuperClasses >= 0) { - if (this.kind.options.maximumNumberOfSuperClasses < this.getDeclaredSuperClasses().length) { - throw new Error(`Only ${this.kind.options.maximumNumberOfSuperClasses} super-classes are allowed.`); + if ( + this.kind.options.maximumNumberOfSuperClasses < + this.getDeclaredSuperClasses().length + ) { + throw new Error( + `Only ${this.kind.options.maximumNumberOfSuperClasses} super-classes are allowed.`, + ); } } }, @@ -141,7 +188,7 @@ export class ClassType extends Type { fields.push(`${field[0]}: ${field[1].getName()}`); } if (fields.length >= 1) { - slots.push(fields.join(', ')); + slots.push(fields.join(", ")); } // methods const methods: string[] = []; @@ -149,48 +196,75 @@ export class ClassType extends Type { methods.push(`${method.getUserRepresentation()}`); } if (methods.length >= 1) { - slots.push(methods.join(', ')); + slots.push(methods.join(", ")); } // super classes const superClasses = this.getDeclaredSuperClasses(); - const extendedClasses = superClasses.length <= 0 ? '' : ` extends ${superClasses.map(c => c.getName()).join(', ')}`; + const extendedClasses = + superClasses.length <= 0 + ? "" + : ` extends ${superClasses.map((c) => c.getName()).join(", ")}`; // complete representation - return `${this.className}${extendedClasses} { ${slots.join(', ')} }`; + return `${this.className}${extendedClasses} { ${slots.join(", ")} }`; } override analyzeTypeEqualityProblems(otherType: Type): TypirProblem[] { if (isClassType(otherType)) { - if (this.kind.options.typing === 'Structural') { + if (this.kind.options.typing === "Structural") { // for structural typing: - return checkNameTypesMap(this.getFields(true), otherType.getFields(true), // including fields of super-classes - (t1, t2) => this.kind.services.Equality.getTypeEqualityProblem(t1, t2)); - } else if (this.kind.options.typing === 'Nominal') { + return checkNameTypesMap( + this.getFields(true), + otherType.getFields(true), // including fields of super-classes + (t1, t2) => + this.kind.services.Equality.getTypeEqualityProblem( + t1, + t2, + ), + ); + } else if (this.kind.options.typing === "Nominal") { // for nominal typing: - return checkValueForConflict(this.getIdentifier(), otherType.getIdentifier(), 'name'); + return checkValueForConflict( + this.getIdentifier(), + otherType.getIdentifier(), + "name", + ); } else { assertUnreachable(this.kind.options.typing); } } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - protected analyzeSubTypeProblems(subType: ClassType, superType: ClassType): TypirProblem[] { - if (this.kind.options.typing === 'Structural') { + protected analyzeSubTypeProblems( + subType: ClassType, + superType: ClassType, + ): TypirProblem[] { + if (this.kind.options.typing === "Structural") { // for structural typing, the sub type needs to have all fields of the super type with assignable types (including fields of all super classes): const conflicts: IndexedTypeConflict[] = []; const subFields = subType.getFields(true); - for (const [superFieldName, superFieldType] of superType.getFields(true)) { + for (const [superFieldName, superFieldType] of superType.getFields( + true, + )) { if (subFields.has(superFieldName)) { // field is both in super and sub const subFieldType = subFields.get(superFieldName)!; - const checkStrategy = createTypeCheckStrategy(this.kind.options.subtypeFieldChecking, this.kind.services); - const subTypeComparison = checkStrategy(subFieldType, superFieldType); + const checkStrategy = createTypeCheckStrategy( + this.kind.options.subtypeFieldChecking, + this.kind.services, + ); + const subTypeComparison = checkStrategy( + subFieldType, + superFieldType, + ); if (subTypeComparison !== undefined) { conflicts.push({ $problem: IndexedTypeConflict, @@ -209,18 +283,22 @@ export class ClassType extends Type { expected: superFieldType, actual: undefined, propertyName: superFieldName, - subProblems: [] + subProblems: [], }); } } // Note that it is not necessary to check, whether the sub class has additional fields than the super type! return conflicts; - } else if (this.kind.options.typing === 'Nominal') { + } else if (this.kind.options.typing === "Nominal") { // for nominal typing (takes super classes into account) const allSub = subType.getAllSuperClasses(true); const globalResult: TypirProblem[] = []; for (const oneSub of allSub) { - const localResult = this.kind.services.Equality.getTypeEqualityProblem(superType, oneSub); + const localResult = + this.kind.services.Equality.getTypeEqualityProblem( + superType, + oneSub, + ); if (localResult === undefined) { return []; // class is found in the class hierarchy } @@ -233,12 +311,12 @@ export class ClassType extends Type { } getDeclaredSuperClasses(): ClassType[] { - return this.superClasses.map(superr => { + return this.superClasses.map((superr) => { const superType = superr.getType(); if (superType) { return superType; } else { - throw new Error('Not all super class types are resolved.'); + throw new Error("Not all super class types are resolved."); } }); } @@ -298,7 +376,9 @@ export class ClassType extends Type { } ensureNoCycles(): void { if (this.hasSubSuperClassCycles()) { - throw new Error('This is not possible, since this class has cycles in its super-classes!'); + throw new Error( + "This is not possible, since this class has cycles in its super-classes!", + ); } } @@ -309,18 +389,20 @@ export class ClassType extends Type { if (withSuperClassesFields) { this.ensureNoCycles(); for (const superClass of this.getDeclaredSuperClasses()) { - for (const [superName, superType] of superClass.getFields(true)) { + for (const [superName, superType] of superClass.getFields( + true, + )) { result.set(superName, superType); } } } // own fields - this.fields.forEach(fieldDetails => { + this.fields.forEach((fieldDetails) => { const field = fieldDetails.type.getType(); if (field) { result.set(fieldDetails.name, field); } else { - throw new Error('Not all fields are resolved.'); + throw new Error("Not all fields are resolved."); } }); return result; @@ -328,12 +410,12 @@ export class ClassType extends Type { getMethods(withSuperClassMethods: boolean): FunctionType[] { // own methods - const result = this.methods.map(m => { + const result = this.methods.map((m) => { const method = m.type.getType(); if (method) { return method; } else { - throw new Error('Not all methods are resolved.'); + throw new Error("Not all methods are resolved."); } }); // methods of super classes @@ -347,7 +429,6 @@ export class ClassType extends Type { } return result; } - } export function isClassType(type: unknown): type is ClassType { diff --git a/packages/typir/src/kinds/class/class-validation.ts b/packages/typir/src/kinds/class/class-validation.ts index fa1ecb6d..4b15537c 100644 --- a/packages/typir/src/kinds/class/class-validation.ts +++ b/packages/typir/src/kinds/class/class-validation.ts @@ -4,32 +4,53 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { ValidationProblemAcceptor, ValidationRule, ValidationRuleLifecycle } from '../../services/validation.js'; -import { TypirServices } from '../../typir.js'; -import { FunctionType, isFunctionType } from '../function/function-type.js'; -import { ClassType, isClassType } from './class-type.js'; +import { + ValidationProblemAcceptor, + ValidationRule, + ValidationRuleLifecycle, +} from "../../services/validation.js"; +import { TypirServices } from "../../typir.js"; +import { FunctionType, isFunctionType } from "../function/function-type.js"; +import { ClassType, isClassType } from "./class-type.js"; /** * Predefined validation to produce errors, if the same class is declared more than once. * This is often relevant for nominally typed classes. */ -export class UniqueClassValidation implements ValidationRuleLifecycle { - protected readonly foundDeclarations: Map = new Map(); +export class UniqueClassValidation + implements ValidationRuleLifecycle +{ + protected readonly foundDeclarations: Map = + new Map(); protected readonly services: TypirServices; - protected readonly isRelevant: ((languageNode: LanguageType) => boolean) | undefined; // using this check improves performance + protected readonly isRelevant: + | ((languageNode: LanguageType) => boolean) + | undefined; // using this check improves performance - constructor(services: TypirServices, isRelevant?: (languageNode: LanguageType) => boolean) { + constructor( + services: TypirServices, + isRelevant?: (languageNode: LanguageType) => boolean, + ) { this.services = services; this.isRelevant = isRelevant; } - beforeValidation(_languageRoot: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { + beforeValidation( + _languageRoot: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { this.foundDeclarations.clear(); } - validation(languageNode: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { - if (this.isRelevant === undefined || this.isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + validation( + languageNode: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + if (this.isRelevant === undefined || this.isRelevant(languageNode)) { + // improves performance, since type inference need to be done only for relevant language nodes const type = this.services.Inference.inferType(languageNode); if (isClassType(type)) { // register language nodes which have ClassTypes with a key for their uniques @@ -56,13 +77,17 @@ export class UniqueClassValidation implements ValidationRuleLifecy return `${clas.className}`; } - afterValidation(_languageRoot: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { + afterValidation( + _languageRoot: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { for (const [key, classes] of this.foundDeclarations.entries()) { if (classes.length >= 2) { for (const clas of classes) { accept({ languageNode: clas, - severity: 'error', + severity: "error", message: `Declared classes need to be unique (${key}).`, }); } @@ -73,7 +98,10 @@ export class UniqueClassValidation implements ValidationRuleLifecy isClassDuplicated(clas: ClassType): boolean { const key = this.calculateClassKey(clas); - return this.foundDeclarations.has(key) && this.foundDeclarations.get(key)!.length >= 2; + return ( + this.foundDeclarations.has(key) && + this.foundDeclarations.get(key)!.length >= 2 + ); } } @@ -82,41 +110,77 @@ interface UniqueMethodValidationEntry { classType: ClassType; } -export interface UniqueMethodValidationOptions { - isMethodDeclaration: (languageNode: LanguageType) => languageNode is T, - getClassOfMethod: (languageNode: T, methodType: FunctionType) => LanguageType, - uniqueClassValidator?: UniqueClassValidation, +export interface UniqueMethodValidationOptions< + LanguageType, + T extends LanguageType = LanguageType, +> { + isMethodDeclaration: (languageNode: LanguageType) => languageNode is T; + getClassOfMethod: ( + languageNode: T, + methodType: FunctionType, + ) => LanguageType; + uniqueClassValidator?: UniqueClassValidation; } /** * Predefined validation to produce errors, if inside a class the same method is declared more than once. */ -export class UniqueMethodValidation implements ValidationRuleLifecycle { - protected readonly foundDeclarations: Map>> = new Map(); +export class UniqueMethodValidation< + LanguageType, + T extends LanguageType = LanguageType, +> implements ValidationRuleLifecycle +{ + protected readonly foundDeclarations: Map< + string, + Array> + > = new Map(); protected readonly services: TypirServices; /** Determines language nodes which represent declared methods, improves performance. */ - protected readonly isMethodDeclaration: (languageNode: LanguageType) => languageNode is T; + protected readonly isMethodDeclaration: ( + languageNode: LanguageType, + ) => languageNode is T; /** Determines the corresponding language node of the class declaration, so that Typir can infer its ClassType */ - protected readonly getClassOfMethod: (languageNode: T, methodType: FunctionType) => LanguageType; - protected readonly uniqueClassValidator: UniqueClassValidation | undefined; + protected readonly getClassOfMethod: ( + languageNode: T, + methodType: FunctionType, + ) => LanguageType; + protected readonly uniqueClassValidator: + | UniqueClassValidation + | undefined; - constructor(services: TypirServices, options: UniqueMethodValidationOptions) { + constructor( + services: TypirServices, + options: UniqueMethodValidationOptions, + ) { this.services = services; this.isMethodDeclaration = options.isMethodDeclaration; this.getClassOfMethod = options.getClassOfMethod; this.uniqueClassValidator = options.uniqueClassValidator; } - beforeValidation(_languageRoot: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { + beforeValidation( + _languageRoot: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { this.foundDeclarations.clear(); } - validation(languageNode: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { - if (this.isMethodDeclaration(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + validation( + languageNode: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + if (this.isMethodDeclaration(languageNode)) { + // improves performance, since type inference need to be done only for relevant language nodes const methodType = this.services.Inference.inferType(languageNode); if (isFunctionType(methodType)) { - const classDeclaration = this.getClassOfMethod(languageNode, methodType); - const classType = this.services.Inference.inferType(classDeclaration); + const classDeclaration = this.getClassOfMethod( + languageNode, + methodType, + ); + const classType = + this.services.Inference.inferType(classDeclaration); if (isClassType(classType)) { const key = this.calculateMethodKey(classType, methodType); let entries = this.foundDeclarations.get(key); @@ -143,19 +207,27 @@ export class UniqueMethodValidation param.type.getIdentifier())})`; + return `${clas.getIdentifier()}.${func.functionName}(${func.getInputs().map((param) => param.type.getIdentifier())})`; } - afterValidation(_LanguageRoot: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { + afterValidation( + _LanguageRoot: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { for (const [key, methods] of this.foundDeclarations.entries()) { if (methods.length >= 2) { for (const method of methods) { - if (this.uniqueClassValidator?.isClassDuplicated(method.classType)) { + if ( + this.uniqueClassValidator?.isClassDuplicated( + method.classType, + ) + ) { // ignore duplicated methods inside duplicated classes } else { accept({ languageNode: method.languageNode, - severity: 'error', + severity: "error", message: `Declared methods need to be unique (${key}).`, }); } @@ -166,7 +238,6 @@ export class UniqueMethodValidation { isRelevant?: (languageNode: LanguageType) => boolean; } @@ -176,16 +247,29 @@ export interface NoSuperClassCyclesValidationOptions { * this parameter is the reason, why this validation cannot be registered by default by Typir for classes, since this parameter is DSL-specific * @returns a validation rule which checks for any class declaration/type, whether they have no cycles in their sub-super-class-relationships */ -export function createNoSuperClassCyclesValidation(options: NoSuperClassCyclesValidationOptions): ValidationRule { - return (languageNode: LanguageType, accept: ValidationProblemAcceptor, typir: TypirServices) => { - if (options.isRelevant === undefined || options.isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes +export function createNoSuperClassCyclesValidation( + options: NoSuperClassCyclesValidationOptions, +): ValidationRule { + return ( + languageNode: LanguageType, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ) => { + if ( + options.isRelevant === undefined || + options.isRelevant(languageNode) + ) { + // improves performance, since type inference need to be done only for relevant language nodes const classType = typir.Inference.inferType(languageNode); - if (isClassType(classType) && classType.isInStateOrLater('Completed')) { + if ( + isClassType(classType) && + classType.isInStateOrLater("Completed") + ) { // check for cycles in sub-type-relationships if (classType.hasSubSuperClassCycles()) { accept({ languageNode: languageNode, - severity: 'error', + severity: "error", message: `Cycles in super-sub-class-relationships are not allowed: ${classType.getName()}`, }); } diff --git a/packages/typir/src/kinds/class/top-class-kind.ts b/packages/typir/src/kinds/class/top-class-kind.ts index 5368c9b7..9e6942b4 100644 --- a/packages/typir/src/kinds/class/top-class-kind.ts +++ b/packages/typir/src/kinds/class/top-class-kind.ts @@ -4,51 +4,66 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { InferCurrentTypeRule, registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; -import { assertTrue } from '../../utils/utils.js'; -import { isKind, Kind } from '../kind.js'; -import { TopClassType } from './top-class-type.js'; +import { TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { + InferCurrentTypeRule, + registerInferCurrentTypeRules, +} from "../../utils/utils-definitions.js"; +import { assertTrue } from "../../utils/utils.js"; +import { isKind, Kind } from "../kind.js"; +import { TopClassType } from "./top-class-type.js"; -export interface TopClassTypeDetails extends TypeDetails { - inferenceRules?: InferCurrentTypeRule | Array> +export interface TopClassTypeDetails + extends TypeDetails { + inferenceRules?: + | InferCurrentTypeRule + | Array>; } export interface TopClassKindOptions { name: string; } -export const TopClassKindName = 'TopClassKind'; +export const TopClassKindName = "TopClassKind"; export class TopClassKind implements Kind { - readonly $name: 'TopClassKind'; + readonly $name: "TopClassKind"; readonly services: TypirServices; readonly options: TopClassKindOptions; protected instance: TopClassType | undefined; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = TopClassKindName; this.services = services; this.services.infrastructure.Kinds.register(this); this.options = this.collectOptions(options); } - protected collectOptions(options?: Partial): TopClassKindOptions { + protected collectOptions( + options?: Partial, + ): TopClassKindOptions { return { // the default values: - name: 'TopClass', + name: "TopClass", // the actually overriden values: - ...options + ...options, }; } - getTopClassType(typeDetails: TopClassTypeDetails): TopClassType | undefined { + getTopClassType( + typeDetails: TopClassTypeDetails, + ): TopClassType | undefined { const key = this.calculateIdentifier(typeDetails); return this.services.infrastructure.Graph.getType(key) as TopClassType; } - createTopClassType(typeDetails: TopClassTypeDetails): TopClassType { + createTopClassType( + typeDetails: TopClassTypeDetails, + ): TopClassType { assertTrue(this.getTopClassType(typeDetails) === undefined); // create the top type (singleton) @@ -56,21 +71,32 @@ export class TopClassKind implements Kind { // note, that the given inference rules are ignored in this case! return this.instance; } - const topType = new TopClassType(this as TopClassKind, this.calculateIdentifier(typeDetails), typeDetails as TopClassTypeDetails); + const topType = new TopClassType( + this as TopClassKind, + this.calculateIdentifier(typeDetails), + typeDetails as TopClassTypeDetails, + ); this.instance = topType; this.services.infrastructure.Graph.addNode(topType); - registerInferCurrentTypeRules(typeDetails.inferenceRules, topType, this.services); + registerInferCurrentTypeRules( + typeDetails.inferenceRules, + topType, + this.services, + ); return topType; } - calculateIdentifier(_typeDetails: TopClassTypeDetails): string { + calculateIdentifier( + _typeDetails: TopClassTypeDetails, + ): string { return this.options.name; } - } -export function isTopClassKind(kind: unknown): kind is TopClassKind { +export function isTopClassKind( + kind: unknown, +): kind is TopClassKind { return isKind(kind) && kind.$name === TopClassKindName; } diff --git a/packages/typir/src/kinds/class/top-class-type.ts b/packages/typir/src/kinds/class/top-class-type.ts index 8c0a8d92..3fe8b10b 100644 --- a/packages/typir/src/kinds/class/top-class-type.ts +++ b/packages/typir/src/kinds/class/top-class-type.ts @@ -4,25 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { createKindConflict } from '../../utils/utils-type-comparison.js'; -import { isClassType } from './class-type.js'; -import { isTopClassKind, TopClassKind, TopClassTypeDetails } from './top-class-kind.js'; +import { TypeGraphListener } from "../../graph/type-graph.js"; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { createKindConflict } from "../../utils/utils-type-comparison.js"; +import { isClassType } from "./class-type.js"; +import { + isTopClassKind, + TopClassKind, + TopClassTypeDetails, +} from "./top-class-kind.js"; export class TopClassType extends Type implements TypeGraphListener { override readonly kind: TopClassKind; - constructor(kind: TopClassKind, identifier: string, typeDetails: TopClassTypeDetails) { + constructor( + kind: TopClassKind, + identifier: string, + typeDetails: TopClassTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; this.defineTheInitializationProcessOfThisType({}); // no preconditions // ensure, that all (other) Class types are a sub-type of this TopClass type: const graph = kind.services.infrastructure.Graph; - graph.getAllRegisteredTypes().forEach(t => this.markAsSubType(t)); // the already existing types + graph.getAllRegisteredTypes().forEach((t) => this.markAsSubType(t)); // the already existing types graph.addListener(this); // all upcomping types } @@ -32,7 +40,9 @@ export class TopClassType extends Type implements TypeGraphListener { protected markAsSubType(type: Type): void { if (type !== this && isClassType(type)) { - this.kind.services.Subtype.markAsSubType(type, this, { checkForCycles: false }); + this.kind.services.Subtype.markAsSubType(type, this, { + checkForCycles: false, + }); } } @@ -52,15 +62,16 @@ export class TopClassType extends Type implements TypeGraphListener { if (isTopClassType(otherType)) { return []; } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - } export function isTopClassType(type: unknown): type is TopClassType { diff --git a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts index 83f53047..8043d031 100644 --- a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts +++ b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts @@ -4,12 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { TypeCheckStrategy } from '../../utils/utils-type-comparison.js'; -import { assertTrue, toArray } from '../../utils/utils.js'; -import { Kind, isKind } from '../kind.js'; -import { FixedParameterType } from './fixed-parameters-type.js'; +import { Type, TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { TypeCheckStrategy } from "../../utils/utils-type-comparison.js"; +import { assertTrue, toArray } from "../../utils/utils.js"; +import { Kind, isKind } from "../kind.js"; +import { FixedParameterType } from "./fixed-parameters-type.js"; export class Parameter { readonly name: string; @@ -21,15 +21,16 @@ export class Parameter { } } -export interface FixedParameterTypeDetails extends TypeDetails { - parameterTypes: Type | Type[] +export interface FixedParameterTypeDetails + extends TypeDetails { + parameterTypes: Type | Type[]; } export interface FixedParameterKindOptions { - parameterSubtypeCheckingStrategy: TypeCheckStrategy, + parameterSubtypeCheckingStrategy: TypeCheckStrategy; } -export const FixedParameterKindName = 'FixedParameterKind'; +export const FixedParameterKindName = "FixedParameterKind"; /** * Suitable for kinds like Collection, List, Array, Map, ..., i.e. types with a fixed number of arbitrary parameter types @@ -41,38 +42,57 @@ export class FixedParameterKind implements Kind { readonly options: Readonly; readonly parameters: Parameter[]; // assumption: the parameters are in the correct order! - constructor(typir: TypirServices, baseName: string, options?: Partial, ...parameterNames: string[]) { + constructor( + typir: TypirServices, + baseName: string, + options?: Partial, + ...parameterNames: string[] + ) { this.$name = `${FixedParameterKindName}-${baseName}`; this.services = typir; this.services.infrastructure.Kinds.register(this); this.baseName = baseName; this.options = this.collectOptions(options); - this.parameters = parameterNames.map((name, index) => { name, index }); + this.parameters = parameterNames.map( + (name, index) => { name, index }, + ); // check input assertTrue(this.parameters.length >= 1); } - protected collectOptions(options?: Partial): FixedParameterKindOptions { + protected collectOptions( + options?: Partial, + ): FixedParameterKindOptions { return { // the default values: - parameterSubtypeCheckingStrategy: 'EQUAL_TYPE', + parameterSubtypeCheckingStrategy: "EQUAL_TYPE", // the actually overriden values: - ...options + ...options, }; } - getFixedParameterType(typeDetails: FixedParameterTypeDetails): FixedParameterType | undefined { + getFixedParameterType( + typeDetails: FixedParameterTypeDetails, + ): FixedParameterType | undefined { const key = this.calculateIdentifier(typeDetails); - return this.services.infrastructure.Graph.getType(key) as FixedParameterType; + return this.services.infrastructure.Graph.getType( + key, + ) as FixedParameterType; } // the order of parameters matters! - createFixedParameterType(typeDetails: FixedParameterTypeDetails): FixedParameterType { + createFixedParameterType( + typeDetails: FixedParameterTypeDetails, + ): FixedParameterType { assertTrue(this.getFixedParameterType(typeDetails) === undefined); // create the class type - const typeWithParameters = new FixedParameterType(this as FixedParameterKind, this.calculateIdentifier(typeDetails), typeDetails); + const typeWithParameters = new FixedParameterType( + this as FixedParameterKind, + this.calculateIdentifier(typeDetails), + typeDetails, + ); this.services.infrastructure.Graph.addNode(typeWithParameters); this.registerInferenceRules(typeDetails, typeWithParameters); @@ -80,20 +100,34 @@ export class FixedParameterKind implements Kind { return typeWithParameters; } - protected registerInferenceRules(_typeDetails: FixedParameterTypeDetails, _typeWithParameters: FixedParameterType): void { + protected registerInferenceRules( + _typeDetails: FixedParameterTypeDetails, + _typeWithParameters: FixedParameterType, + ): void { // TODO } - calculateIdentifier(typeDetails: FixedParameterTypeDetails): string { - return this.printSignature(this.baseName, toArray(typeDetails.parameterTypes), ','); // use the signature for a unique name + calculateIdentifier( + typeDetails: FixedParameterTypeDetails, + ): string { + return this.printSignature( + this.baseName, + toArray(typeDetails.parameterTypes), + ",", + ); // use the signature for a unique name } - printSignature(baseName: string, parameterTypes: Type[], parameterSeparator: string): string { - return `${baseName}<${parameterTypes.map(p => p.getName()).join(parameterSeparator)}>`; + printSignature( + baseName: string, + parameterTypes: Type[], + parameterSeparator: string, + ): string { + return `${baseName}<${parameterTypes.map((p) => p.getName()).join(parameterSeparator)}>`; } - } -export function isFixedParametersKind(kind: unknown): kind is FixedParameterKind { - return isKind(kind) && kind.$name.startsWith('FixedParameterKind-'); +export function isFixedParametersKind( + kind: unknown, +): kind is FixedParameterKind { + return isKind(kind) && kind.$name.startsWith("FixedParameterKind-"); } diff --git a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts index 7ffebe15..81f8c7e1 100644 --- a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts +++ b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts @@ -4,12 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { checkTypeArrays, checkValueForConflict, createKindConflict, createTypeCheckStrategy } from '../../utils/utils-type-comparison.js'; -import { assertTrue, toArray } from '../../utils/utils.js'; -import { FixedParameterKind, FixedParameterTypeDetails, isFixedParametersKind, Parameter } from './fixed-parameters-kind.js'; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { + checkTypeArrays, + checkValueForConflict, + createKindConflict, + createTypeCheckStrategy, +} from "../../utils/utils-type-comparison.js"; +import { assertTrue, toArray } from "../../utils/utils.js"; +import { + FixedParameterKind, + FixedParameterTypeDetails, + isFixedParametersKind, + Parameter, +} from "./fixed-parameters-kind.js"; export class ParameterValue { readonly parameter: Parameter; @@ -25,7 +35,11 @@ export class FixedParameterType extends Type { override readonly kind: FixedParameterKind; readonly parameterValues: ParameterValue[] = []; - constructor(kind: FixedParameterKind, identifier: string, typeDetails: FixedParameterTypeDetails) { + constructor( + kind: FixedParameterKind, + identifier: string, + typeDetails: FixedParameterTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; @@ -42,11 +56,11 @@ export class FixedParameterType extends Type { } getParameterTypes(): Type[] { - return this.parameterValues.map(p => p.type); + return this.parameterValues.map((p) => p.type); } override getName(): string { - return `${this.kind.printSignature(this.kind.baseName, this.getParameterTypes(), ', ')}`; + return `${this.kind.printSignature(this.kind.baseName, this.getParameterTypes(), ", ")}`; } override getUserRepresentation(): string { @@ -56,40 +70,74 @@ export class FixedParameterType extends Type { override analyzeTypeEqualityProblems(otherType: Type): TypirProblem[] { if (isFixedParameterType(otherType)) { // same name, e.g. both need to be Map, Set, Array, ... - const baseTypeCheck = checkValueForConflict(this.kind.baseName, otherType.kind.baseName, 'base type'); + const baseTypeCheck = checkValueForConflict( + this.kind.baseName, + otherType.kind.baseName, + "base type", + ); if (baseTypeCheck.length >= 1) { // e.g. List !== Set return baseTypeCheck; } else { // all parameter types must match, e.g. Set !== Set const conflicts: TypirProblem[] = []; - conflicts.push(...checkTypeArrays(this.getParameterTypes(), otherType.getParameterTypes(), (t1, t2) => this.kind.services.Equality.getTypeEqualityProblem(t1, t2), false)); + conflicts.push( + ...checkTypeArrays( + this.getParameterTypes(), + otherType.getParameterTypes(), + (t1, t2) => + this.kind.services.Equality.getTypeEqualityProblem( + t1, + t2, + ), + false, + ), + ); return conflicts; } } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(this, otherType)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(this, otherType)], + }, + ]; } } - protected analyzeSubTypeProblems(subType: FixedParameterType, superType: FixedParameterType): TypirProblem[] { + protected analyzeSubTypeProblems( + subType: FixedParameterType, + superType: FixedParameterType, + ): TypirProblem[] { // same name, e.g. both need to be Map, Set, Array, ... - const baseTypeCheck = checkValueForConflict(subType.kind.baseName, superType.kind.baseName, 'base type'); + const baseTypeCheck = checkValueForConflict( + subType.kind.baseName, + superType.kind.baseName, + "base type", + ); if (baseTypeCheck.length >= 1) { // e.g. List !== Set return baseTypeCheck; } else { // all parameter types must match, e.g. Set !== Set - const checkStrategy = createTypeCheckStrategy(this.kind.options.parameterSubtypeCheckingStrategy, this.kind.services); - return checkTypeArrays(subType.getParameterTypes(), superType.getParameterTypes(), checkStrategy, false); + const checkStrategy = createTypeCheckStrategy( + this.kind.options.parameterSubtypeCheckingStrategy, + this.kind.services, + ); + return checkTypeArrays( + subType.getParameterTypes(), + superType.getParameterTypes(), + checkStrategy, + false, + ); } } } -export function isFixedParameterType(type: unknown): type is FixedParameterType { +export function isFixedParameterType( + type: unknown, +): type is FixedParameterType { return isType(type) && isFixedParametersKind(type.kind); } diff --git a/packages/typir/src/kinds/function/function-inference-call.ts b/packages/typir/src/kinds/function/function-inference-call.ts index b0ef4d08..9787080b 100644 --- a/packages/typir/src/kinds/function/function-inference-call.ts +++ b/packages/typir/src/kinds/function/function-inference-call.ts @@ -4,14 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from '../../graph/type-node.js'; -import { AssignabilitySuccess, isAssignabilityProblem } from '../../services/assignability.js'; -import { InferenceProblem, InferenceRuleNotApplicable, TypeInferenceResultWithInferringChildren, TypeInferenceRuleWithInferringChildren } from '../../services/inference.js'; -import { TypirServices } from '../../typir.js'; -import { checkTypeArrays } from '../../utils/utils-type-comparison.js'; -import { FunctionTypeDetails, InferFunctionCall } from './function-kind.js'; -import { AvailableFunctionsManager } from './function-overloading.js'; -import { FunctionType } from './function-type.js'; +import { Type } from "../../graph/type-node.js"; +import { + AssignabilitySuccess, + isAssignabilityProblem, +} from "../../services/assignability.js"; +import { + InferenceProblem, + InferenceRuleNotApplicable, + TypeInferenceResultWithInferringChildren, + TypeInferenceRuleWithInferringChildren, +} from "../../services/inference.js"; +import { TypirServices } from "../../typir.js"; +import { checkTypeArrays } from "../../utils/utils-type-comparison.js"; +import { FunctionTypeDetails, InferFunctionCall } from "./function-kind.js"; +import { AvailableFunctionsManager } from "./function-overloading.js"; +import { FunctionType } from "./function-type.js"; /** * Dedicated inference rule for calls of a single function signature. @@ -25,40 +33,69 @@ import { FunctionType } from './function-type.js'; * - the current function has an output type/parameter, otherwise, this function could not provide any type (and throws an error), when it is called! * (exception: the options contain a type to return in this special case) */ -export class FunctionCallInferenceRule implements TypeInferenceRuleWithInferringChildren { +export class FunctionCallInferenceRule< + LanguageType, + T extends LanguageType = LanguageType, +> implements TypeInferenceRuleWithInferringChildren +{ protected readonly typeDetails: FunctionTypeDetails; - protected readonly inferenceRuleForCalls: InferFunctionCall; + protected readonly inferenceRuleForCalls: InferFunctionCall< + LanguageType, + T + >; protected readonly functionType: FunctionType; protected readonly functions: AvailableFunctionsManager; assignabilitySuccess: Array; // public, since this information is exploited to determine the best overloaded match in case of multiple matches - constructor(typeDetails: FunctionTypeDetails, inferenceRuleForCalls: InferFunctionCall, functionType: FunctionType, functions: AvailableFunctionsManager) { + constructor( + typeDetails: FunctionTypeDetails, + inferenceRuleForCalls: InferFunctionCall, + functionType: FunctionType, + functions: AvailableFunctionsManager, + ) { this.typeDetails = typeDetails; this.inferenceRuleForCalls = inferenceRuleForCalls; this.functionType = functionType; this.functions = functions; - this.assignabilitySuccess = new Array(typeDetails.inputParameters.length); + this.assignabilitySuccess = new Array( + typeDetails.inputParameters.length, + ); } - inferTypeWithoutChildren(languageNode: LanguageType, _typir: TypirServices): TypeInferenceResultWithInferringChildren { + inferTypeWithoutChildren( + languageNode: LanguageType, + _typir: TypirServices, + ): TypeInferenceResultWithInferringChildren { this.assignabilitySuccess.fill(undefined); // reset the entries // 0. The LanguageKeys are already checked by OverloadedFunctionsTypeInferenceRule, nothing to do here // 1. Does the filter of the inference rule accept the current language node? - const result = this.inferenceRuleForCalls.filter === undefined || this.inferenceRuleForCalls.filter(languageNode); + const result = + this.inferenceRuleForCalls.filter === undefined || + this.inferenceRuleForCalls.filter(languageNode); if (!result) { // the language node has a completely different purpose return InferenceRuleNotApplicable; } // 2. Does the inference rule match this language node? - const matching = this.inferenceRuleForCalls.matching === undefined || this.inferenceRuleForCalls.matching(languageNode as T, this.functionType); + const matching = + this.inferenceRuleForCalls.matching === undefined || + this.inferenceRuleForCalls.matching( + languageNode as T, + this.functionType, + ); if (!matching) { // the language node is slightly different return InferenceRuleNotApplicable; } // 3. Check whether the current arguments fit to the expected parameter types // 3a. Check some special cases, in order to save the effort to do type inference for the given arguments - const overloadInfos = this.functions.getOverloads(this.typeDetails.functionName); - if (overloadInfos === undefined || overloadInfos.overloadedFunctions.length <= 1) { + const overloadInfos = this.functions.getOverloads( + this.typeDetails.functionName, + ); + if ( + overloadInfos === undefined || + overloadInfos.overloadedFunctions.length <= 1 + ) { // the current function is not overloaded, therefore, the types of their parameters are not required => save time, ignore inference errors return this.check(this.getOutputTypeForFunctionCalls()); } @@ -68,18 +105,29 @@ export class FunctionCallInferenceRule infer the types of the parameters now - const inputArguments = this.inferenceRuleForCalls.inputArguments(languageNode as T); + const inputArguments = this.inferenceRuleForCalls.inputArguments( + languageNode as T, + ); return inputArguments; } - inferTypeWithChildrensTypes(languageNode: LanguageType, actualInputTypes: Array, typir: TypirServices): Type | InferenceProblem { - const expectedInputTypes = this.typeDetails.inputParameters.map(p => typir.infrastructure.TypeResolver.resolve(p.type)); + inferTypeWithChildrensTypes( + languageNode: LanguageType, + actualInputTypes: Array, + typir: TypirServices, + ): Type | InferenceProblem { + const expectedInputTypes = this.typeDetails.inputParameters.map((p) => + typir.infrastructure.TypeResolver.resolve(p.type), + ); // all operands need to be assignable(! not equal) to the required types const comparisonConflicts = checkTypeArrays( actualInputTypes, expectedInputTypes, (t1, t2, index) => { - const result = typir.Assignability.getAssignabilityResult(t1, t2); + const result = typir.Assignability.getAssignabilityResult( + t1, + t2, + ); if (isAssignabilityProblem(result)) { return result; } else { @@ -96,7 +144,7 @@ export class FunctionCallInferenceRule extends CompositeTypeInferenceRule { - - protected override inferTypeLogic(languageNode: LanguageType): Type | Array> { +export class OverloadedFunctionsTypeInferenceRule< + LanguageType, +> extends CompositeTypeInferenceRule { + protected override inferTypeLogic( + languageNode: LanguageType, + ): Type | Array> { this.checkForError(languageNode); // check all rules in order to search for the best-matching rule, not for the first-matching rule const matchingOverloads: Array> = []; - const collectedInferenceProblems: Array> = []; + const collectedInferenceProblems: Array< + InferenceProblem + > = []; // execute the rules which are associated to the key of the current language node - const languageKey = this.services.Language.getLanguageNodeKey(languageNode); - for (const rule of this.ruleRegistry.getRulesByLanguageKey(languageKey)) { - const result = this.executeSingleInferenceRuleLogic(rule, languageNode, collectedInferenceProblems); + const languageKey = + this.services.Language.getLanguageNodeKey(languageNode); + for (const rule of this.ruleRegistry.getRulesByLanguageKey( + languageKey, + )) { + const result = this.executeSingleInferenceRuleLogic( + rule, + languageNode, + collectedInferenceProblems, + ); if (result) { - matchingOverloads.push({ result, rule: rule as FunctionCallInferenceRule }); + matchingOverloads.push({ + result, + rule: rule as FunctionCallInferenceRule, + }); } else { // no result for this inference rule => check the next inference rules } } // execute all rules which are associated to no language nodes at all (as a fall-back for such rules) if (languageKey !== undefined) { - for (const rule of this.ruleRegistry.getRulesByLanguageKey(undefined)) { - const result = this.executeSingleInferenceRuleLogic(rule, languageNode, collectedInferenceProblems); + for (const rule of this.ruleRegistry.getRulesByLanguageKey( + undefined, + )) { + const result = this.executeSingleInferenceRuleLogic( + rule, + languageNode, + collectedInferenceProblems, + ); if (result) { - matchingOverloads.push({ result, rule: rule as FunctionCallInferenceRule }); + matchingOverloads.push({ + result, + rule: rule as FunctionCallInferenceRule, + }); } else { // no result for this inference rule => check the next inference rules } @@ -56,7 +83,7 @@ export class OverloadedFunctionsTypeInferenceRule extends Composit collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: 'found no applicable inference rules', + location: "found no applicable inference rules", subProblems: [], }); } @@ -68,10 +95,15 @@ export class OverloadedFunctionsTypeInferenceRule extends Composit // multiple matches => determine the one to return // 1. identify and collect the best matches - const bestMatches: Array> = [ matchingOverloads[0] ]; + const bestMatches: Array> = [ + matchingOverloads[0], + ]; for (let i = 1; i < matchingOverloads.length; i++) { const currentMatch = matchingOverloads[i]; - const comparison = this.compareMatchingOverloads(bestMatches[0], currentMatch); + const comparison = this.compareMatchingOverloads( + bestMatches[0], + currentMatch, + ); if (comparison < 0) { // the existing matches are better than the current one => keep the existing best matches } else if (comparison > 0) { @@ -95,34 +127,50 @@ export class OverloadedFunctionsTypeInferenceRule extends Composit return result.result; } else { // no decision => inference is not possible - return [{ - $problem: InferenceProblem, - languageNode: languageNode, - location: `Found ${bestMatches.length} best matching overloads: ${bestMatches.map(m => m.result.getIdentifier()).join(', ')}`, - subProblems: [], // there are no real sub-problems, since the relevant overloads match ... - }]; + return [ + { + $problem: InferenceProblem, + languageNode: languageNode, + location: `Found ${bestMatches.length} best matching overloads: ${bestMatches.map((m) => m.result.getIdentifier()).join(", ")}`, + subProblems: [], // there are no real sub-problems, since the relevant overloads match ... + }, + ]; } } } } - protected handleMultipleBestMatches(matchingOverloads: Array>): OverloadedMatch | undefined { + protected handleMultipleBestMatches( + matchingOverloads: Array>, + ): OverloadedMatch | undefined { return matchingOverloads[0]; // by default, return the 1st best match } // better matches are at the beginning of the list, i.e. better matches get values lower than zero - protected compareMatchingOverloads(match1: OverloadedMatch, match2: OverloadedMatch): number { + protected compareMatchingOverloads( + match1: OverloadedMatch, + match2: OverloadedMatch, + ): number { const cost1 = this.calculateCost(match1); const cost2 = this.calculateCost(match2); return cost1 === cost2 ? 0 : cost1 < cost2 ? -1 : +1; } protected calculateCost(match: OverloadedMatch): number { - return match.rule.assignabilitySuccess // one path (consisting of an arbitrary number of edges) for each parameter - .flatMap(s => s?.path ?? []) // collect all conversion/sub-type edges which are required to map actual types to the expected types of the parameters - // equal types (i.e. an empty path) are better than sub-types, sub-types are better than conversions - .map(edge => (isSubTypeEdge(edge) ? 1 : isConversionEdge(edge) ? 2 : assertUnreachable(edge)) as number) - .reduce((l, r) => l + r, 0); // sum of all costs + return ( + match.rule.assignabilitySuccess // one path (consisting of an arbitrary number of edges) for each parameter + .flatMap((s) => s?.path ?? []) // collect all conversion/sub-type edges which are required to map actual types to the expected types of the parameters + // equal types (i.e. an empty path) are better than sub-types, sub-types are better than conversions + .map( + (edge) => + (isSubTypeEdge(edge) + ? 1 + : isConversionEdge(edge) + ? 2 + : assertUnreachable(edge)) as number, + ) + .reduce((l, r) => l + r, 0) + ); // sum of all costs } } diff --git a/packages/typir/src/kinds/function/function-initializer.ts b/packages/typir/src/kinds/function/function-initializer.ts index 40fcb223..9afb62fc 100644 --- a/packages/typir/src/kinds/function/function-initializer.ts +++ b/packages/typir/src/kinds/function/function-initializer.ts @@ -4,16 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeStateListener } from '../../graph/type-node.js'; -import { TypeInitializer } from '../../initialization/type-initializer.js'; -import { TypeInferenceRule } from '../../services/inference.js'; -import { TypirServices } from '../../typir.js'; -import { bindInferCurrentTypeRule, InferenceRuleWithOptions, optionsBoundToType } from '../../utils/utils-definitions.js'; -import { assertTypirType } from '../../utils/utils.js'; -import { FunctionCallInferenceRule } from './function-inference-call.js'; -import { CreateFunctionTypeDetails, FunctionKind, FunctionTypeDetails, InferFunctionCall } from './function-kind.js'; -import { AvailableFunctionsManager } from './function-overloading.js'; -import { FunctionType, isFunctionType } from './function-type.js'; +import { Type, TypeStateListener } from "../../graph/type-node.js"; +import { TypeInitializer } from "../../initialization/type-initializer.js"; +import { TypeInferenceRule } from "../../services/inference.js"; +import { TypirServices } from "../../typir.js"; +import { + bindInferCurrentTypeRule, + InferenceRuleWithOptions, + optionsBoundToType, +} from "../../utils/utils-definitions.js"; +import { assertTypirType } from "../../utils/utils.js"; +import { FunctionCallInferenceRule } from "./function-inference-call.js"; +import { + CreateFunctionTypeDetails, + FunctionKind, + FunctionTypeDetails, + InferFunctionCall, +} from "./function-kind.js"; +import { AvailableFunctionsManager } from "./function-overloading.js"; +import { FunctionType, isFunctionType } from "./function-type.js"; /** * For each call of FunctionKind.create()...finish(), one instance of this class will be created, @@ -21,13 +30,20 @@ import { FunctionType, isFunctionType } from './function-type.js'; * * If the function type to create already exists, the given inference rules (and its validation rules) will be registered for the existing function type. */ -export class FunctionTypeInitializer extends TypeInitializer implements TypeStateListener { +export class FunctionTypeInitializer + extends TypeInitializer + implements TypeStateListener +{ protected readonly typeDetails: CreateFunctionTypeDetails; protected readonly functions: AvailableFunctionsManager; protected inferenceRules: FunctionInferenceRules; protected initialFunctionType: FunctionType; - constructor(services: TypirServices, kind: FunctionKind, typeDetails: CreateFunctionTypeDetails) { + constructor( + services: TypirServices, + kind: FunctionKind, + typeDetails: CreateFunctionTypeDetails, + ) { super(services); this.typeDetails = typeDetails; this.functions = kind.functions; @@ -35,16 +51,29 @@ export class FunctionTypeInitializer extends TypeInitializer= 1) { + if ( + typeDetails.outputParameter === undefined && + typeDetails.inferenceRulesForCalls.length >= 1 + ) { // no output parameter => no inference rule for calling this function - throw new Error(`A function '${functionName}' without output parameter cannot have an inferred type, when this function is called!`); + throw new Error( + `A function '${functionName}' without output parameter cannot have an inferred type, when this function is called!`, + ); } - kind.enforceFunctionName(functionName, kind.options.enforceFunctionName); + kind.enforceFunctionName( + functionName, + kind.options.enforceFunctionName, + ); // create the new Function type - this.initialFunctionType = new FunctionType(kind as FunctionKind, typeDetails as FunctionTypeDetails); - - this.inferenceRules = this.createInferenceRules(this.initialFunctionType); + this.initialFunctionType = new FunctionType( + kind as FunctionKind, + typeDetails as FunctionTypeDetails, + ); + + this.inferenceRules = this.createInferenceRules( + this.initialFunctionType, + ); this.registerRules(functionName, undefined); this.initialFunctionType.addListener(this, true); @@ -69,7 +98,10 @@ export class FunctionTypeInitializer extends TypeInitializer extends TypeInitializer { + protected createInferenceRules( + functionType: FunctionType, + ): FunctionInferenceRules { const result: FunctionInferenceRules = { inferenceForCall: [], inferenceForDeclaration: [], @@ -120,14 +173,24 @@ export class FunctionTypeInitializer extends TypeInitializer, functionType: FunctionType): TypeInferenceRule { - return new FunctionCallInferenceRule(this.typeDetails, rule, functionType, this.functions); + protected createFunctionCallInferenceRule( + rule: InferFunctionCall, + functionType: FunctionType, + ): TypeInferenceRule { + return new FunctionCallInferenceRule( + this.typeDetails, + rule, + functionType, + this.functions, + ); } } diff --git a/packages/typir/src/kinds/function/function-kind.ts b/packages/typir/src/kinds/function/function-kind.ts index d3eabee2..785b8bfc 100644 --- a/packages/typir/src/kinds/function/function-kind.ts +++ b/packages/typir/src/kinds/function/function-kind.ts @@ -4,55 +4,68 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from '../../graph/type-node.js'; -import { TypeInitializer } from '../../initialization/type-initializer.js'; -import { TypeReference } from '../../initialization/type-reference.js'; -import { TypeSelector } from '../../initialization/type-selector.js'; -import { ValidationRule } from '../../services/validation.js'; -import { TypirServices } from '../../typir.js'; -import { InferCurrentTypeRule, NameTypePair, RegistrationOptions } from '../../utils/utils-definitions.js'; -import { TypeCheckStrategy } from '../../utils/utils-type-comparison.js'; -import { isKind, Kind } from '../kind.js'; -import { FunctionTypeInitializer } from './function-initializer.js'; -import { AvailableFunctionsManager } from './function-overloading.js'; -import { FunctionType } from './function-type.js'; -import { UniqueFunctionValidation } from './function-validation-unique.js'; - +import { Type, TypeDetails } from "../../graph/type-node.js"; +import { TypeInitializer } from "../../initialization/type-initializer.js"; +import { TypeReference } from "../../initialization/type-reference.js"; +import { TypeSelector } from "../../initialization/type-selector.js"; +import { ValidationRule } from "../../services/validation.js"; +import { TypirServices } from "../../typir.js"; +import { + InferCurrentTypeRule, + NameTypePair, + RegistrationOptions, +} from "../../utils/utils-definitions.js"; +import { TypeCheckStrategy } from "../../utils/utils-type-comparison.js"; +import { isKind, Kind } from "../kind.js"; +import { FunctionTypeInitializer } from "./function-initializer.js"; +import { AvailableFunctionsManager } from "./function-overloading.js"; +import { FunctionType } from "./function-type.js"; +import { UniqueFunctionValidation } from "./function-validation-unique.js"; export interface FunctionKindOptions { // these three options controls structural vs nominal typing somehow ... - enforceFunctionName: boolean, - enforceInputParameterNames: boolean, - enforceOutputParameterName: boolean, + enforceFunctionName: boolean; + enforceInputParameterNames: boolean; + enforceOutputParameterName: boolean; /** Will be used only internally as prefix for the unique identifiers for function type names. */ - identifierPrefix: string, + identifierPrefix: string; /** If a function has no output type (e.g. "void" functions), this type is returned during the type inference of calls to these functions. * The default value "THROW_ERROR" indicates to throw an error, i.e. type inference for calls of such functions are not allowed. */ - typeToInferForCallsOfFunctionsWithoutOutput: 'THROW_ERROR' | TypeSelector; + typeToInferForCallsOfFunctionsWithoutOutput: + | "THROW_ERROR" + | TypeSelector; subtypeParameterChecking: TypeCheckStrategy; } -export const FunctionKindName = 'FunctionKind'; - +export const FunctionKindName = "FunctionKind"; export interface CreateParameterDetails { name: string; type: TypeSelector; } -export interface FunctionTypeDetails extends TypeDetails { - functionName: string, +export interface FunctionTypeDetails + extends TypeDetails { + functionName: string; /** The order of parameters is important! */ - outputParameter: CreateParameterDetails | undefined, - inputParameters: Array>, + outputParameter: CreateParameterDetails | undefined; + inputParameters: Array>; } -export interface CreateFunctionTypeDetails extends FunctionTypeDetails { - inferenceRulesForDeclaration: Array>, - inferenceRulesForCalls: Array>, +export interface CreateFunctionTypeDetails + extends FunctionTypeDetails { + inferenceRulesForDeclaration: Array< + InferCurrentTypeRule + >; + inferenceRulesForCalls: Array< + InferFunctionCall + >; } -export interface InferFunctionCall extends InferCurrentTypeRule { +export interface InferFunctionCall< + LanguageType, + T extends LanguageType = LanguageType, +> extends InferCurrentTypeRule { /** * In case of overloaded functions, these input arguments are used to determine the actual function * by comparing the types of the given arguments with the expected types of the input parameters of the function. @@ -105,25 +118,34 @@ export interface InferFunctionCall { - create(typeDetails: FunctionTypeDetails): FunctionConfigurationChain; - get(typeDetails: FunctionTypeDetails): TypeReference; + create( + typeDetails: FunctionTypeDetails, + ): FunctionConfigurationChain; + get( + typeDetails: FunctionTypeDetails, + ): TypeReference; calculateIdentifier(typeDetails: FunctionTypeDetails): string; // some predefined valitions: /** Creates a validation rule which checks, that the function types are unique. */ - createUniqueFunctionValidation(options: RegistrationOptions): ValidationRule; + createUniqueFunctionValidation( + options: RegistrationOptions, + ): ValidationRule; // benefits of this design decision: the returned rule is easier to exchange, users can use the known factory API with auto-completion (no need to remember the names of the validations) } export interface FunctionConfigurationChain { /** for function declarations => returns the funtion type (the whole signature including all names) */ - inferenceRuleForDeclaration(rule: InferCurrentTypeRule): FunctionConfigurationChain; + inferenceRuleForDeclaration( + rule: InferCurrentTypeRule, + ): FunctionConfigurationChain; /** for function calls => returns the return type of the function */ - inferenceRuleForCalls(rule: InferFunctionCall): FunctionConfigurationChain, + inferenceRuleForCalls( + rule: InferFunctionCall, + ): FunctionConfigurationChain; // TODO for function references (like the declaration, but without any names!) => returns signature (without any names) @@ -146,13 +168,18 @@ export interface FunctionConfigurationChain { * - optional parameters * - parameters which are used for output AND input */ -export class FunctionKind implements Kind, FunctionFactoryService { - readonly $name: 'FunctionKind'; +export class FunctionKind + implements Kind, FunctionFactoryService +{ + readonly $name: "FunctionKind"; readonly services: TypirServices; readonly options: Readonly>; readonly functions: AvailableFunctionsManager; - constructor(services: TypirServices, options?: Partial>) { + constructor( + services: TypirServices, + options?: Partial>, + ) { this.$name = FunctionKindName; this.services = services; this.services.infrastructure.Kinds.register(this); @@ -160,17 +187,19 @@ export class FunctionKind implements Kind, FunctionFactoryService< this.functions = this.createFunctionManager(); } - protected collectOptions(options?: Partial>): FunctionKindOptions { + protected collectOptions( + options?: Partial>, + ): FunctionKindOptions { return { // the default values: enforceFunctionName: false, enforceInputParameterNames: false, enforceOutputParameterName: false, - identifierPrefix: 'function', - typeToInferForCallsOfFunctionsWithoutOutput: 'THROW_ERROR', - subtypeParameterChecking: 'SUB_TYPE', + identifierPrefix: "function", + typeToInferForCallsOfFunctionsWithoutOutput: "THROW_ERROR", + subtypeParameterChecking: "SUB_TYPE", // the actually overriden values: - ...options + ...options, }; } @@ -178,31 +207,65 @@ export class FunctionKind implements Kind, FunctionFactoryService< return new AvailableFunctionsManager(this.services, this); } - get(typeDetails: FunctionTypeDetails): TypeReference { - return new TypeReference(() => this.calculateIdentifier(typeDetails), this.services); + get( + typeDetails: FunctionTypeDetails, + ): TypeReference { + return new TypeReference( + () => this.calculateIdentifier(typeDetails), + this.services, + ); } - create(typeDetails: FunctionTypeDetails): FunctionConfigurationChain { - return new FunctionConfigurationChainImpl(this.services, this, typeDetails); + create( + typeDetails: FunctionTypeDetails, + ): FunctionConfigurationChain { + return new FunctionConfigurationChainImpl( + this.services, + this, + typeDetails, + ); } - getOutputTypeForFunctionCalls(functionType: FunctionType): Type | undefined { - return functionType.getOutput('RETURN_UNDEFINED')?.type ?? // by default, use the return type of the function ... + getOutputTypeForFunctionCalls( + functionType: FunctionType, + ): Type | undefined { + return ( + functionType.getOutput("RETURN_UNDEFINED")?.type ?? // by default, use the return type of the function ... // ... if this type is missing, use the specified type for this case in the options: // 'THROW_ERROR': an error will be thrown later, when this case actually occurs! - (this.options.typeToInferForCallsOfFunctionsWithoutOutput === 'THROW_ERROR' + (this.options.typeToInferForCallsOfFunctionsWithoutOutput === + "THROW_ERROR" ? undefined - : this.services.infrastructure.TypeResolver.resolve(this.options.typeToInferForCallsOfFunctionsWithoutOutput)); + : this.services.infrastructure.TypeResolver.resolve( + this.options.typeToInferForCallsOfFunctionsWithoutOutput, + )) + ); } - calculateIdentifier(typeDetails: FunctionTypeDetails): string { - const prefix = this.options.identifierPrefix ? this.options.identifierPrefix + '-' : ''; + calculateIdentifier( + typeDetails: FunctionTypeDetails, + ): string { + const prefix = this.options.identifierPrefix + ? this.options.identifierPrefix + "-" + : ""; // function name, if wanted - const functionName = this.hasFunctionName(typeDetails.functionName) ? typeDetails.functionName : ''; + const functionName = this.hasFunctionName(typeDetails.functionName) + ? typeDetails.functionName + : ""; // inputs: type identifiers in defined order - const inputsString = typeDetails.inputParameters.map(input => this.services.infrastructure.TypeResolver.resolve(input.type).getIdentifier()).join(','); + const inputsString = typeDetails.inputParameters + .map((input) => + this.services.infrastructure.TypeResolver.resolve( + input.type, + ).getIdentifier(), + ) + .join(","); // output: type identifier - const outputString = typeDetails.outputParameter ? this.services.infrastructure.TypeResolver.resolve(typeDetails.outputParameter.type).getIdentifier() : ''; + const outputString = typeDetails.outputParameter + ? this.services.infrastructure.TypeResolver.resolve( + typeDetails.outputParameter.type, + ).getIdentifier() + : ""; // complete signature return `${prefix}${functionName}(${inputsString}):${outputString}`; } @@ -218,7 +281,7 @@ export class FunctionKind implements Kind, FunctionFactoryService< enforceFunctionName(name: string | undefined, enforce: boolean): void { if (enforce && this.hasFunctionName(name) === false) { - throw new Error('A name for the function is required.'); + throw new Error("A name for the function is required."); } } hasFunctionName(name: string | undefined): name is string { @@ -227,35 +290,47 @@ export class FunctionKind implements Kind, FunctionFactoryService< enforceParameterName(name: string | undefined, enforce: boolean): void { if (enforce && this.hasParameterName(name) === false) { - throw new Error('A name for the parameter is required.'); + throw new Error("A name for the parameter is required."); } } hasParameterName(name: string | undefined): name is string { return name !== undefined && name !== NO_PARAMETER_NAME; } - createUniqueFunctionValidation(options: RegistrationOptions): ValidationRule { + createUniqueFunctionValidation( + options: RegistrationOptions, + ): ValidationRule { const rule = new UniqueFunctionValidation(this.services); - if (options.registration === 'MYSELF') { + if (options.registration === "MYSELF") { // do nothing, the user is responsible to register the rule } else { - this.services.validation.Collector.addValidationRule(rule, options.registration); + this.services.validation.Collector.addValidationRule( + rule, + options.registration, + ); } return rule; } } -export function isFunctionKind(kind: unknown): kind is FunctionKind { +export function isFunctionKind( + kind: unknown, +): kind is FunctionKind { return isKind(kind) && kind.$name === FunctionKindName; } - -class FunctionConfigurationChainImpl implements FunctionConfigurationChain { +class FunctionConfigurationChainImpl + implements FunctionConfigurationChain +{ protected readonly services: TypirServices; protected readonly kind: FunctionKind; protected readonly currentFunctionDetails: CreateFunctionTypeDetails; - constructor(services: TypirServices, kind: FunctionKind, typeDetails: FunctionTypeDetails) { + constructor( + services: TypirServices, + kind: FunctionKind, + typeDetails: FunctionTypeDetails, + ) { this.services = services; this.kind = kind; this.currentFunctionDetails = { @@ -265,21 +340,33 @@ class FunctionConfigurationChainImpl implements FunctionConfigurat }; } - inferenceRuleForDeclaration(rule: InferCurrentTypeRule): FunctionConfigurationChain { - this.currentFunctionDetails.inferenceRulesForDeclaration.push(rule as unknown as InferCurrentTypeRule); + inferenceRuleForDeclaration( + rule: InferCurrentTypeRule, + ): FunctionConfigurationChain { + this.currentFunctionDetails.inferenceRulesForDeclaration.push( + rule as unknown as InferCurrentTypeRule, + ); return this; } - inferenceRuleForCalls(rule: InferFunctionCall): FunctionConfigurationChain { - this.currentFunctionDetails.inferenceRulesForCalls.push(rule as unknown as InferFunctionCall); + inferenceRuleForCalls( + rule: InferFunctionCall, + ): FunctionConfigurationChain { + this.currentFunctionDetails.inferenceRulesForCalls.push( + rule as unknown as InferFunctionCall, + ); return this; } finish(): TypeInitializer { - return new FunctionTypeInitializer(this.services, this.kind, this.currentFunctionDetails); + return new FunctionTypeInitializer( + this.services, + this.kind, + this.currentFunctionDetails, + ); } } // when the name is missing (e.g. for functions or their input/output parameters), use these values instead -export const NO_FUNCTION_NAME = ''; -export const NO_PARAMETER_NAME = ''; +export const NO_FUNCTION_NAME = ""; +export const NO_PARAMETER_NAME = ""; diff --git a/packages/typir/src/kinds/function/function-overloading.ts b/packages/typir/src/kinds/function/function-overloading.ts index f2466544..5ce56c4a 100644 --- a/packages/typir/src/kinds/function/function-overloading.ts +++ b/packages/typir/src/kinds/function/function-overloading.ts @@ -4,16 +4,16 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; -import { Type } from '../../graph/type-node.js'; -import { CompositeTypeInferenceRule } from '../../services/inference.js'; -import { TypirServices } from '../../typir.js'; -import { RuleRegistry } from '../../utils/rule-registration.js'; -import { removeFromArray } from '../../utils/utils.js'; -import { OverloadedFunctionsTypeInferenceRule } from './function-inference-overloaded.js'; -import { FunctionKind, InferFunctionCall } from './function-kind.js'; -import { FunctionType, isFunctionType } from './function-type.js'; -import { FunctionCallArgumentsValidation } from './function-validation-calls.js'; +import { TypeGraphListener } from "../../graph/type-graph.js"; +import { Type } from "../../graph/type-node.js"; +import { CompositeTypeInferenceRule } from "../../services/inference.js"; +import { TypirServices } from "../../typir.js"; +import { RuleRegistry } from "../../utils/rule-registration.js"; +import { removeFromArray } from "../../utils/utils.js"; +import { OverloadedFunctionsTypeInferenceRule } from "./function-inference-overloaded.js"; +import { FunctionKind, InferFunctionCall } from "./function-kind.js"; +import { FunctionType, isFunctionType } from "./function-type.js"; +import { FunctionCallArgumentsValidation } from "./function-validation-calls.js"; /** * Collects information about all functions with the same name. @@ -30,18 +30,22 @@ export interface OverloadedFunctionDetails { sameOutputType: Type | undefined; } -export interface SingleFunctionDetails { +export interface SingleFunctionDetails< + LanguageType, + T extends LanguageType = LanguageType, +> { functionType: FunctionType; inferenceRuleForCalls: InferFunctionCall; } - /** * Contains all the logic to manage all available functions, * in particular, to support overloaded functions. * In each type system, exactly one instance of this class is stored by the FunctionKind. */ -export class AvailableFunctionsManager implements TypeGraphListener { +export class AvailableFunctionsManager + implements TypeGraphListener +{ protected readonly services: TypirServices; protected readonly kind: FunctionKind; @@ -54,18 +58,25 @@ export class AvailableFunctionsManager implements TypeGraphListene * the corresponding rules and logic need to involve multiple types, * which makes it more complex and requires to manage them here and not in the single types. */ - protected readonly mapNameTypes: Map> = new Map(); + protected readonly mapNameTypes: Map< + string, + OverloadedFunctionDetails + > = new Map(); protected readonly validatorArgumentsCalls: FunctionCallArgumentsValidation; - constructor(services: TypirServices, kind: FunctionKind) { + constructor( + services: TypirServices, + kind: FunctionKind, + ) { this.services = services; this.kind = kind; this.services.infrastructure.Graph.addListener(this); // this validation rule for checking arguments of function calls exists "for ever", since it validates all function types - this.validatorArgumentsCalls = this.createFunctionCallArgumentsValidation(); + this.validatorArgumentsCalls = + this.createFunctionCallArgumentsValidation(); } protected createFunctionCallArgumentsValidation(): FunctionCallArgumentsValidation { @@ -75,15 +86,21 @@ export class AvailableFunctionsManager implements TypeGraphListene protected createInferenceRuleForOverloads(): CompositeTypeInferenceRule { // This inference rule don't need to be registered at the Inference service, since it manages the (de)registrations itself! - return new OverloadedFunctionsTypeInferenceRule(this.services, this.services.Inference); + return new OverloadedFunctionsTypeInferenceRule( + this.services, + this.services.Inference, + ); } - - getOverloads(functionName: string): OverloadedFunctionDetails | undefined { + getOverloads( + functionName: string, + ): OverloadedFunctionDetails | undefined { return this.mapNameTypes.get(functionName); } - getOrCreateOverloads(functionName: string): OverloadedFunctionDetails { + getOrCreateOverloads( + functionName: string, + ): OverloadedFunctionDetails { let result = this.mapNameTypes.get(functionName); if (result === undefined) { result = { @@ -99,12 +116,21 @@ export class AvailableFunctionsManager implements TypeGraphListene return result; } - getAllOverloads(): MapIterator<[string, OverloadedFunctionDetails]> { + getAllOverloads(): MapIterator< + [string, OverloadedFunctionDetails] + > { return this.mapNameTypes.entries(); } - addFunction(readyFunctionType: FunctionType, inferenceRulesForCalls: Array>): void { - const overloaded = this.getOrCreateOverloads(readyFunctionType.functionName); + addFunction( + readyFunctionType: FunctionType, + inferenceRulesForCalls: Array< + InferFunctionCall + >, + ): void { + const overloaded = this.getOrCreateOverloads( + readyFunctionType.functionName, + ); // remember the function type itself overloaded.overloadedFunctions.push(readyFunctionType); @@ -112,13 +138,18 @@ export class AvailableFunctionsManager implements TypeGraphListene this.calculateSameOutputType(overloaded); // register each inference rule for calls of the function - inferenceRulesForCalls.forEach(rule => overloaded.details.addRule({ - functionType: readyFunctionType, - inferenceRuleForCalls: rule, - }, { - languageKey: rule.languageKey, // the language keys are directly encoded inside these special inference rules for function calls - boundToType: readyFunctionType, // these rules are specific for current function type/signature - })); + inferenceRulesForCalls.forEach((rule) => + overloaded.details.addRule( + { + functionType: readyFunctionType, + inferenceRuleForCalls: rule, + }, + { + languageKey: rule.languageKey, // the language keys are directly encoded inside these special inference rules for function calls + boundToType: readyFunctionType, // these rules are specific for current function type/signature + }, + ), + ); } /* Get informed about deleted types in order to remove inference rules which are bound to them. */ @@ -127,7 +158,10 @@ export class AvailableFunctionsManager implements TypeGraphListene const overloaded = this.getOverloads(type.functionName); if (overloaded) { // remove the current function - const removed = removeFromArray(type, overloaded.overloadedFunctions); + const removed = removeFromArray( + type, + overloaded.overloadedFunctions, + ); if (removed) { this.calculateSameOutputType(overloaded); } @@ -137,15 +171,29 @@ export class AvailableFunctionsManager implements TypeGraphListene } } - protected calculateSameOutputType(overloaded: OverloadedFunctionDetails): void { + protected calculateSameOutputType( + overloaded: OverloadedFunctionDetails, + ): void { overloaded.sameOutputType = undefined; - for (let index = 0; index < overloaded.overloadedFunctions.length; index++) { + for ( + let index = 0; + index < overloaded.overloadedFunctions.length; + index++ + ) { const current = overloaded.overloadedFunctions[index]; - const outputTypeForFunctionCalls = this.kind.getOutputTypeForFunctionCalls(current); // output parameter for function calls + const outputTypeForFunctionCalls = + this.kind.getOutputTypeForFunctionCalls(current); // output parameter for function calls if (index === 0) { overloaded.sameOutputType = outputTypeForFunctionCalls; } else { - if (overloaded.sameOutputType && outputTypeForFunctionCalls && this.services.Equality.areTypesEqual(overloaded.sameOutputType, outputTypeForFunctionCalls) === true) { + if ( + overloaded.sameOutputType && + outputTypeForFunctionCalls && + this.services.Equality.areTypesEqual( + overloaded.sameOutputType, + outputTypeForFunctionCalls, + ) === true + ) { // the output types of all overloaded functions are the same for now } else { // there is a difference @@ -155,5 +203,4 @@ export class AvailableFunctionsManager implements TypeGraphListene } } } - } diff --git a/packages/typir/src/kinds/function/function-type.ts b/packages/typir/src/kinds/function/function-type.ts index 5ca4126c..2c607c16 100644 --- a/packages/typir/src/kinds/function/function-type.ts +++ b/packages/typir/src/kinds/function/function-type.ts @@ -4,13 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, isType } from '../../graph/type-node.js'; -import { TypeReference } from '../../initialization/type-reference.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { NameTypePair, TypirProblem } from '../../utils/utils-definitions.js'; -import { checkTypeArrays, checkTypes, checkValueForConflict, createKindConflict, createTypeCheckStrategy } from '../../utils/utils-type-comparison.js'; -import { assertTrue, assertUnreachable } from '../../utils/utils.js'; -import { FunctionKind, FunctionTypeDetails, isFunctionKind } from './function-kind.js'; +import { Type, isType } from "../../graph/type-node.js"; +import { TypeReference } from "../../initialization/type-reference.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { NameTypePair, TypirProblem } from "../../utils/utils-definitions.js"; +import { + checkTypeArrays, + checkTypes, + checkValueForConflict, + createKindConflict, + createTypeCheckStrategy, +} from "../../utils/utils-type-comparison.js"; +import { assertTrue, assertUnreachable } from "../../utils/utils.js"; +import { + FunctionKind, + FunctionTypeDetails, + isFunctionKind, +} from "./function-kind.js"; export interface ParameterDetails { name: string; @@ -24,16 +34,27 @@ export class FunctionType extends Type { readonly outputParameter: ParameterDetails | undefined; readonly inputParameters: ParameterDetails[]; - constructor(kind: FunctionKind, typeDetails: FunctionTypeDetails) { + constructor( + kind: FunctionKind, + typeDetails: FunctionTypeDetails, + ) { super(undefined, typeDetails); this.kind = kind; this.functionName = typeDetails.functionName; // output parameter - const outputType = typeDetails.outputParameter ? new TypeReference(typeDetails.outputParameter.type, this.kind.services) : undefined; + const outputType = typeDetails.outputParameter + ? new TypeReference( + typeDetails.outputParameter.type, + this.kind.services, + ) + : undefined; if (typeDetails.outputParameter) { assertTrue(outputType !== undefined); - this.kind.enforceParameterName(typeDetails.outputParameter.name, this.kind.options.enforceOutputParameterName); + this.kind.enforceParameterName( + typeDetails.outputParameter.name, + this.kind.options.enforceOutputParameterName, + ); this.outputParameter = { name: typeDetails.outputParameter.name, type: outputType, @@ -44,8 +65,11 @@ export class FunctionType extends Type { } // input parameters - this.inputParameters = typeDetails.inputParameters.map(input => { - this.kind.enforceParameterName(input.name, this.kind.options.enforceInputParameterNames); + this.inputParameters = typeDetails.inputParameters.map((input) => { + this.kind.enforceParameterName( + input.name, + this.kind.options.enforceInputParameterNames, + ); return { name: input.name, type: new TypeReference(input.type, this.kind.services), @@ -53,7 +77,7 @@ export class FunctionType extends Type { }); // define to wait for the parameter types - const allParameterRefs = this.inputParameters.map(p => p.type); + const allParameterRefs = this.inputParameters.map((p) => p.type); if (outputType) { allParameterRefs.push(outputType); } @@ -85,18 +109,22 @@ export class FunctionType extends Type { const simpleFunctionName = this.getSimpleFunctionName(); // inputs const inputs = this.getInputs(); - const inputsString = inputs.map(input => this.kind.getParameterRepresentation(input)).join(', '); + const inputsString = inputs + .map((input) => this.kind.getParameterRepresentation(input)) + .join(", "); // output const output = this.getOutput(); const outputString = output - ? (this.kind.hasParameterName(output.name) ? `(${this.kind.getParameterRepresentation(output)})` : output.type.getName()) + ? this.kind.hasParameterName(output.name) + ? `(${this.kind.getParameterRepresentation(output)})` + : output.type.getName() : undefined; // complete signature if (this.kind.hasFunctionName(simpleFunctionName)) { - const outputValue = outputString ? `: ${outputString}` : ''; + const outputValue = outputString ? `: ${outputString}` : ""; return `${simpleFunctionName}(${inputsString})${outputValue}`; } else { - return `(${inputsString}) => ${outputString ?? '()'}`; + return `(${inputsString}) => ${outputString ?? "()"}`; } } @@ -105,34 +133,80 @@ export class FunctionType extends Type { const conflicts: TypirProblem[] = []; // same name? since functions with different names are different if (this.kind.options.enforceFunctionName) { - conflicts.push(...checkValueForConflict(this.getSimpleFunctionName(), otherType.getSimpleFunctionName(), 'simple name')); + conflicts.push( + ...checkValueForConflict( + this.getSimpleFunctionName(), + otherType.getSimpleFunctionName(), + "simple name", + ), + ); } // same output? - conflicts.push(...checkTypes(this.getOutput(), otherType.getOutput(), - (s, t) => this.kind.services.Equality.getTypeEqualityProblem(s, t), this.kind.options.enforceOutputParameterName)); + conflicts.push( + ...checkTypes( + this.getOutput(), + otherType.getOutput(), + (s, t) => + this.kind.services.Equality.getTypeEqualityProblem( + s, + t, + ), + this.kind.options.enforceOutputParameterName, + ), + ); // same input? - conflicts.push(...checkTypeArrays(this.getInputs(), otherType.getInputs(), - (s, t) => this.kind.services.Equality.getTypeEqualityProblem(s, t), this.kind.options.enforceInputParameterNames)); + conflicts.push( + ...checkTypeArrays( + this.getInputs(), + otherType.getInputs(), + (s, t) => + this.kind.services.Equality.getTypeEqualityProblem( + s, + t, + ), + this.kind.options.enforceInputParameterNames, + ), + ); return conflicts; } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - protected analyzeSubTypeProblems(subType: FunctionType, superType: FunctionType): TypirProblem[] { + protected analyzeSubTypeProblems( + subType: FunctionType, + superType: FunctionType, + ): TypirProblem[] { const conflicts: TypirProblem[] = []; - const strategy = createTypeCheckStrategy(this.kind.options.subtypeParameterChecking, this.kind.services); + const strategy = createTypeCheckStrategy( + this.kind.options.subtypeParameterChecking, + this.kind.services, + ); // output: sub type output must be assignable (which can be configured) to super type output - conflicts.push(...checkTypes(subType.getOutput(), superType.getOutput(), - (sub, superr) => strategy(sub, superr), this.kind.options.enforceOutputParameterName)); + conflicts.push( + ...checkTypes( + subType.getOutput(), + superType.getOutput(), + (sub, superr) => strategy(sub, superr), + this.kind.options.enforceOutputParameterName, + ), + ); // input: super type inputs must be assignable (which can be configured) to sub type inputs - conflicts.push(...checkTypeArrays(subType.getInputs(), superType.getInputs(), - (sub, superr) => strategy(superr, sub), this.kind.options.enforceInputParameterNames)); + conflicts.push( + ...checkTypeArrays( + subType.getInputs(), + superType.getInputs(), + (sub, superr) => strategy(superr, sub), + this.kind.options.enforceInputParameterNames, + ), + ); return conflicts; } @@ -140,7 +214,9 @@ export class FunctionType extends Type { return this.functionName; } - getOutput(notResolvedBehavior: 'EXCEPTION' | 'RETURN_UNDEFINED' = 'EXCEPTION'): NameTypePair | undefined { + getOutput( + notResolvedBehavior: "EXCEPTION" | "RETURN_UNDEFINED" = "EXCEPTION", + ): NameTypePair | undefined { if (this.outputParameter) { const type = this.outputParameter.type.getType(); if (type) { @@ -150,9 +226,11 @@ export class FunctionType extends Type { }; } else { switch (notResolvedBehavior) { - case 'EXCEPTION': - throw new Error(`Output parameter ${this.outputParameter.name} is not resolved.`); - case 'RETURN_UNDEFINED': + case "EXCEPTION": + throw new Error( + `Output parameter ${this.outputParameter.name} is not resolved.`, + ); + case "RETURN_UNDEFINED": return undefined; default: assertUnreachable(notResolvedBehavior); @@ -164,7 +242,7 @@ export class FunctionType extends Type { } getInputs(): NameTypePair[] { - return this.inputParameters.map(param => { + return this.inputParameters.map((param) => { const type = param.type.getType(); if (type) { return { @@ -172,7 +250,9 @@ export class FunctionType extends Type { type, }; } else { - throw new Error(`Input parameter ${param.name} is not resolved.`); + throw new Error( + `Input parameter ${param.name} is not resolved.`, + ); } }); } diff --git a/packages/typir/src/kinds/function/function-validation-calls.ts b/packages/typir/src/kinds/function/function-validation-calls.ts index 3855c8ef..a6c389f4 100644 --- a/packages/typir/src/kinds/function/function-validation-calls.ts +++ b/packages/typir/src/kinds/function/function-validation-calls.ts @@ -2,15 +2,29 @@ * Copyright 2025 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ + ******************************************************************************/ -import { ValidationProblem, ValidationProblemAcceptor, ValidationRuleLifecycle } from '../../services/validation.js'; -import { TypirServices } from '../../typir.js'; -import { RuleCollectorListener, RuleOptions } from '../../utils/rule-registration.js'; -import { checkTypes, checkValueForConflict, createTypeCheckStrategy } from '../../utils/utils-type-comparison.js'; -import { assertUnreachable, toArray } from '../../utils/utils.js'; -import { InferFunctionCall } from './function-kind.js'; -import { AvailableFunctionsManager, SingleFunctionDetails } from './function-overloading.js'; +import { + ValidationProblem, + ValidationProblemAcceptor, + ValidationRuleLifecycle, +} from "../../services/validation.js"; +import { TypirServices } from "../../typir.js"; +import { + RuleCollectorListener, + RuleOptions, +} from "../../utils/rule-registration.js"; +import { + checkTypes, + checkValueForConflict, + createTypeCheckStrategy, +} from "../../utils/utils-type-comparison.js"; +import { assertUnreachable, toArray } from "../../utils/utils.js"; +import { InferFunctionCall } from "./function-kind.js"; +import { + AvailableFunctionsManager, + SingleFunctionDetails, +} from "./function-overloading.js"; /** * This validation uses the inference rules for all available function calls to check, whether ... @@ -18,16 +32,26 @@ import { AvailableFunctionsManager, SingleFunctionDetails } from './function-ove * - and validates this call according to the specific validation rules for this function call. * There is only one instance of this class for each function kind/manager. */ -export class FunctionCallArgumentsValidation implements ValidationRuleLifecycle, RuleCollectorListener> { +export class FunctionCallArgumentsValidation + implements + ValidationRuleLifecycle, + RuleCollectorListener> +{ protected readonly services: TypirServices; readonly functions: AvailableFunctionsManager; - constructor(services: TypirServices, functions: AvailableFunctionsManager) { + constructor( + services: TypirServices, + functions: AvailableFunctionsManager, + ) { this.services = services; this.functions = functions; } - onAddedRule(_rule: SingleFunctionDetails, diffOptions: RuleOptions): void { + onAddedRule( + _rule: SingleFunctionDetails, + diffOptions: RuleOptions, + ): void { // this rule needs to be registered also for all the language keys of the new inner function call rule this.services.validation.Collector.addValidationRule(this, { ...diffOptions, @@ -35,7 +59,10 @@ export class FunctionCallArgumentsValidation implements Validation }); } - onRemovedRule(_rule: SingleFunctionDetails, diffOptions: RuleOptions): void { + onRemovedRule( + _rule: SingleFunctionDetails, + diffOptions: RuleOptions, + ): void { // remove this "composite" rule for all language keys for which no function call rules are registered anymore if (diffOptions.languageKey === undefined) { if (this.noFunctionCallRulesForThisLanguageKey(undefined)) { @@ -46,7 +73,9 @@ export class FunctionCallArgumentsValidation implements Validation }); } } else { - const languageKeysToUnregister = toArray(diffOptions.languageKey).filter(key => this.noFunctionCallRulesForThisLanguageKey(key)); + const languageKeysToUnregister = toArray( + diffOptions.languageKey, + ).filter((key) => this.noFunctionCallRulesForThisLanguageKey(key)); this.services.validation.Collector.removeValidationRule(this, { ...diffOptions, languageKey: languageKeysToUnregister, @@ -55,7 +84,9 @@ export class FunctionCallArgumentsValidation implements Validation } } - protected noFunctionCallRulesForThisLanguageKey(key: undefined | string): boolean { + protected noFunctionCallRulesForThisLanguageKey( + key: undefined | string, + ): boolean { for (const overloads of this.functions.getAllOverloads()) { if (overloads[1].details.getRulesByLanguageKey(key).length >= 1) { return false; @@ -64,38 +95,71 @@ export class FunctionCallArgumentsValidation implements Validation return true; } - validation(languageNode: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { + validation( + languageNode: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { // determine all keys to check - const keysToApply: Array = []; - const languageKey = this.services.Language.getLanguageNodeKey(languageNode); + const keysToApply: Array = []; + const languageKey = + this.services.Language.getLanguageNodeKey(languageNode); if (languageKey === undefined) { keysToApply.push(undefined); } else { keysToApply.push(languageKey); // execute the rules which are associated to the key of the current language node - keysToApply.push(...this.services.Language.getAllSuperKeys(languageKey)); // apply all rules which are associated to super-keys + keysToApply.push( + ...this.services.Language.getAllSuperKeys(languageKey), + ); // apply all rules which are associated to super-keys keysToApply.push(undefined); // rules associated with 'undefined' are applied to all language nodes, apply these rules at the end } // execute all rules wich are associated to the relevant language keys - const alreadyExecutedRules: Set> = new Set(); + const alreadyExecutedRules: Set> = + new Set(); // for each (overloaded) function - for (const [overloadedName, overloadedFunctions] of this.functions.getAllOverloads()) { // this grouping is not required here (but for other use cases) and does not hurt here + for (const [ + overloadedName, + overloadedFunctions, + ] of this.functions.getAllOverloads()) { + // this grouping is not required here (but for other use cases) and does not hurt here const resultOverloaded: Array> = []; // for each language key for (const key of keysToApply) { - for (const singleFunction of overloadedFunctions.details.getRulesByLanguageKey(key)) { - if (alreadyExecutedRules.has(singleFunction.inferenceRuleForCalls)) { // TODO funktioniert das überhaupt, sprich: wird immer ein neues Objekt erstellt oder das aus der Konfiguration durchgereicht? zumindestens für Operatoren + for (const singleFunction of overloadedFunctions.details.getRulesByLanguageKey( + key, + )) { + if ( + alreadyExecutedRules.has( + singleFunction.inferenceRuleForCalls, + ) + ) { + // TODO funktioniert das überhaupt, sprich: wird immer ein neues Objekt erstellt oder das aus der Konfiguration durchgereicht? zumindestens für Operatoren // don't execute rules multiple times, if they are associated with multiple keys (with overlapping sub-keys) } else { - const exactMatch = this.executeSingleRule(singleFunction, languageNode, resultOverloaded); + const exactMatch = this.executeSingleRule( + singleFunction, + languageNode, + resultOverloaded, + ); if (exactMatch) { // found exact match => execute the validation rules which are specific for this function call ... - for (const specificValidation of toArray(singleFunction.inferenceRuleForCalls.validation)) { - specificValidation.call(specificValidation, languageNode, singleFunction.functionType, accept, this.services); + for (const specificValidation of toArray( + singleFunction.inferenceRuleForCalls.validation, + )) { + specificValidation.call( + specificValidation, + languageNode, + singleFunction.functionType, + accept, + this.services, + ); } return; // ... and ignore the other function call rules } - alreadyExecutedRules.add(singleFunction.inferenceRuleForCalls); + alreadyExecutedRules.add( + singleFunction.inferenceRuleForCalls, + ); } } } @@ -103,8 +167,8 @@ export class FunctionCallArgumentsValidation implements Validation if (resultOverloaded.length >= 1) { accept({ languageNode: languageNode, - severity: 'error', - message: `The given operands for the call of ${overloadedFunctions.overloadedFunctions.length >= 2 ? 'the overload ' : ''}'${overloadedName}' don't match.`, + severity: "error", + message: `The given operands for the call of ${overloadedFunctions.overloadedFunctions.length >= 2 ? "the overload " : ""}'${overloadedName}' don't match.`, subProblems: resultOverloaded, }); } @@ -117,14 +181,24 @@ export class FunctionCallArgumentsValidation implements Validation * @param languageNode the current language node, which might or might not represent a function call * @param resultOverloaded receives a validation issue, if there is at least one conflict between given arguments and expected parameters * @returns true, if the given function signature exactly matches the current function call, false otherwise - */ - protected executeSingleRule(singleFunction: SingleFunctionDetails, languageNode: LanguageType, resultOverloaded: Array>): boolean { + */ + protected executeSingleRule( + singleFunction: SingleFunctionDetails, + languageNode: LanguageType, + resultOverloaded: Array>, + ): boolean { const inferenceRule = singleFunction.inferenceRuleForCalls; const functionType = singleFunction.functionType; - if (inferenceRule.filter !== undefined && inferenceRule.filter(languageNode) === false) { + if ( + inferenceRule.filter !== undefined && + inferenceRule.filter(languageNode) === false + ) { return false; // rule does not match at all => no constraints apply here => no error to show here } - if (inferenceRule.matching !== undefined && inferenceRule.matching(languageNode, functionType) === false) { + if ( + inferenceRule.matching !== undefined && + inferenceRule.matching(languageNode, functionType) === false + ) { return false; // false => does slightly not match => no constraints apply here => no error to show here } @@ -134,29 +208,41 @@ export class FunctionCallArgumentsValidation implements Validation const inputArguments = inferenceRule.inputArguments(languageNode); const expectedParameterTypes = functionType.getInputs(); // check, that the given number of parameters is the same as the expected number of input parameters - const parameterLength = checkValueForConflict(expectedParameterTypes.length, inputArguments.length, 'number of input parameter values'); + const parameterLength = checkValueForConflict( + expectedParameterTypes.length, + inputArguments.length, + "number of input parameter values", + ); if (parameterLength.length >= 1) { currentProblems.push({ $problem: ValidationProblem, languageNode: languageNode, - severity: 'error', - message: 'The number of given parameter values does not match the expected number of input parameters.', + severity: "error", + message: + "The number of given parameter values does not match the expected number of input parameters.", subProblems: parameterLength, }); } else { // compare arguments with their corresponding parameters - const inferredParameterTypes = inputArguments.map(p => this.services.Inference.inferType(p)); + const inferredParameterTypes = inputArguments.map((p) => + this.services.Inference.inferType(p), + ); for (let i = 0; i < inputArguments.length; i++) { const expectedType = expectedParameterTypes[i]; const inferredType = inferredParameterTypes[i]; - const parameterProblems = checkTypes(inferredType, expectedType, createTypeCheckStrategy('ASSIGNABLE_TYPE', this.services), true); + const parameterProblems = checkTypes( + inferredType, + expectedType, + createTypeCheckStrategy("ASSIGNABLE_TYPE", this.services), + true, + ); if (parameterProblems.length >= 1) { // the value is not assignable to the type of the input parameter // create one ValidationProblem for each problematic parameter! currentProblems.push({ $problem: ValidationProblem, languageNode: inputArguments[i], - severity: 'error', + severity: "error", message: `The parameter '${expectedType.name}' at index ${i} got a value with a wrong type.`, subProblems: parameterProblems, }); @@ -169,11 +255,16 @@ export class FunctionCallArgumentsValidation implements Validation // summarize all parameters of the current function overload/signature if (currentProblems.length >= 1) { // some problems with parameters => this signature does not match - if (this.validateArgumentsOfFunctionCalls(inferenceRule, languageNode)) { + if ( + this.validateArgumentsOfFunctionCalls( + inferenceRule, + languageNode, + ) + ) { resultOverloaded.push({ $problem: ValidationProblem, languageNode: languageNode, - severity: 'error', + severity: "error", message: `The given arguments don't match the parameters of '${this.services.Printer.printTypeUserRepresentation(functionType)}'.`, subProblems: currentProblems, }); @@ -186,16 +277,20 @@ export class FunctionCallArgumentsValidation implements Validation } } - protected validateArgumentsOfFunctionCalls(rule: InferFunctionCall, languageNode: LanguageType): boolean { + protected validateArgumentsOfFunctionCalls( + rule: InferFunctionCall, + languageNode: LanguageType, + ): boolean { if (rule.validateArgumentsOfFunctionCalls === undefined) { return false; // the default value - } else if (typeof rule.validateArgumentsOfFunctionCalls === 'boolean') { + } else if (typeof rule.validateArgumentsOfFunctionCalls === "boolean") { return rule.validateArgumentsOfFunctionCalls; - } else if (typeof rule.validateArgumentsOfFunctionCalls === 'function') { + } else if ( + typeof rule.validateArgumentsOfFunctionCalls === "function" + ) { return rule.validateArgumentsOfFunctionCalls(languageNode); } else { assertUnreachable(rule.validateArgumentsOfFunctionCalls); } } - } diff --git a/packages/typir/src/kinds/function/function-validation-unique.ts b/packages/typir/src/kinds/function/function-validation-unique.ts index b9ba6a08..dde0f7ea 100644 --- a/packages/typir/src/kinds/function/function-validation-unique.ts +++ b/packages/typir/src/kinds/function/function-validation-unique.ts @@ -4,16 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { ValidationProblemAcceptor, ValidationRuleLifecycle } from '../../services/validation.js'; -import { TypirServices } from '../../typir.js'; -import { FunctionType, isFunctionType } from './function-type.js'; +import { + ValidationProblemAcceptor, + ValidationRuleLifecycle, +} from "../../services/validation.js"; +import { TypirServices } from "../../typir.js"; +import { FunctionType, isFunctionType } from "./function-type.js"; /** * Predefined validation to produce errors for those (overloaded) functions which cannot be distinguished when calling them. * By default, only the name and the types of the input parameters are used to distinguish functions. */ -export class UniqueFunctionValidation implements ValidationRuleLifecycle { - protected readonly foundDeclarations: Map = new Map(); +export class UniqueFunctionValidation + implements ValidationRuleLifecycle +{ + protected readonly foundDeclarations: Map = + new Map(); protected readonly services: TypirServices; /** * Use this check to filter language nodes which are relevant for the creation of functions, @@ -21,19 +27,33 @@ export class UniqueFunctionValidation implements ValidationRuleLif * Beyond that, this check improves performance, since type inference will be done only for the filtered language nodes. * Instead of using this filter, the 'language key' to register this validation rules can be exploited for the same purposes. */ - protected readonly isRelevant: ((languageNode: LanguageType) => boolean) | undefined; + protected readonly isRelevant: + | ((languageNode: LanguageType) => boolean) + | undefined; - constructor(services: TypirServices, isRelevant?: (languageNode: LanguageType) => boolean) { + constructor( + services: TypirServices, + isRelevant?: (languageNode: LanguageType) => boolean, + ) { this.services = services; this.isRelevant = isRelevant; } - beforeValidation(_languageRoot: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { + beforeValidation( + _languageRoot: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { this.foundDeclarations.clear(); } - validation(languageNode: LanguageType, _accept: ValidationProblemAcceptor, _typir: TypirServices): void { - if (this.isRelevant === undefined || this.isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + validation( + languageNode: LanguageType, + _accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + if (this.isRelevant === undefined || this.isRelevant(languageNode)) { + // improves performance, since type inference need to be done only for relevant language nodes const type = this.services.Inference.inferType(languageNode); if (isFunctionType(type)) { // register language nodes which have FunctionTypes with a key for their uniqueness @@ -57,16 +77,20 @@ export class UniqueFunctionValidation implements ValidationRuleLif * @returns a string key */ protected calculateFunctionKey(func: FunctionType): string { - return `${func.functionName}(${func.getInputs().map(param => param.type.getIdentifier())})`; + return `${func.functionName}(${func.getInputs().map((param) => param.type.getIdentifier())})`; } - afterValidation(_languageRoot: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { + afterValidation( + _languageRoot: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { for (const [key, functions] of this.foundDeclarations.entries()) { if (functions.length >= 2) { for (const func of functions) { accept({ languageNode: func, - severity: 'error', + severity: "error", message: `Declared functions need to be unique (${key}).`, }); } diff --git a/packages/typir/src/kinds/kind.ts b/packages/typir/src/kinds/kind.ts index e42bc6e0..f7c624bd 100644 --- a/packages/typir/src/kinds/kind.ts +++ b/packages/typir/src/kinds/kind.ts @@ -4,7 +4,6 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ - /** * Typir provides a default set of Kinds, e.g. primitive types and class types. * For domain-specific kinds, implement this interface or create a new sub-class of an existing kind-class. @@ -27,9 +26,12 @@ export interface Kind { - used to uniquely identify same types! (not overloaded types) - must be public in order to reuse it by other Kinds */ - } export function isKind(kind: unknown): kind is Kind { - return typeof kind === 'object' && kind !== null && typeof (kind as Kind).$name === 'string'; + return ( + typeof kind === "object" && + kind !== null && + typeof (kind as Kind).$name === "string" + ); } diff --git a/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts b/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts index 3a655c59..517b7852 100644 --- a/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts +++ b/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts @@ -4,16 +4,17 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { assertTrue } from '../../utils/utils.js'; -import { Kind, isKind } from '../kind.js'; -import { MultiplicityType } from './multiplicity-type.js'; - -export interface MultiplicityTypeDetails extends TypeDetails { - constrainedType: Type, - lowerBound: number, - upperBound: number +import { Type, TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { assertTrue } from "../../utils/utils.js"; +import { Kind, isKind } from "../kind.js"; +import { MultiplicityType } from "./multiplicity-type.js"; + +export interface MultiplicityTypeDetails + extends TypeDetails { + constrainedType: Type; + lowerBound: number; + upperBound: number; } export interface MultiplicityKindOptions { @@ -21,39 +22,50 @@ export interface MultiplicityKindOptions { } export const MULTIPLICITY_UNLIMITED = -1; -export const MultiplicityKindName = 'MultiplicityTypeKind'; +export const MultiplicityKindName = "MultiplicityTypeKind"; /** * Types of this kind constrain a type with lower bound and upper bound, * e.g. ConstrainedType[1..*] or ConstrainedType[2..4]. */ export class MultiplicityKind implements Kind { - readonly $name: 'MultiplicityTypeKind'; + readonly $name: "MultiplicityTypeKind"; readonly services: TypirServices; readonly options: Readonly; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = MultiplicityKindName; this.services = services; this.services.infrastructure.Kinds.register(this); this.options = this.collectOptions(options); } - protected collectOptions(options?: Partial): MultiplicityKindOptions { + protected collectOptions( + options?: Partial, + ): MultiplicityKindOptions { return { // the default values: - symbolForUnlimited: '*', + symbolForUnlimited: "*", // the actually overriden values: - ...options + ...options, }; } - getMultiplicityType(typeDetails: MultiplicityTypeDetails): MultiplicityType | undefined { + getMultiplicityType( + typeDetails: MultiplicityTypeDetails, + ): MultiplicityType | undefined { const key = this.calculateIdentifier(typeDetails); - return this.services.infrastructure.Graph.getType(key) as MultiplicityType; + return this.services.infrastructure.Graph.getType( + key, + ) as MultiplicityType; } - createMultiplicityType(typeDetails: MultiplicityTypeDetails): MultiplicityType { + createMultiplicityType( + typeDetails: MultiplicityTypeDetails, + ): MultiplicityType { // check input assertTrue(this.getMultiplicityType(typeDetails) === undefined); if (!this.checkBounds(typeDetails.lowerBound, typeDetails.upperBound)) { @@ -61,7 +73,11 @@ export class MultiplicityKind implements Kind { } // create the type with multiplicities - const typeWithMultiplicity = new MultiplicityType(this as MultiplicityKind, this.calculateIdentifier(typeDetails), typeDetails); + const typeWithMultiplicity = new MultiplicityType( + this as MultiplicityKind, + this.calculateIdentifier(typeDetails), + typeDetails, + ); this.services.infrastructure.Graph.addNode(typeWithMultiplicity); this.registerInferenceRules(typeDetails, typeWithMultiplicity); @@ -69,11 +85,16 @@ export class MultiplicityKind implements Kind { return typeWithMultiplicity; } - protected registerInferenceRules(_typeDetails: MultiplicityTypeDetails, _typeWithMultiplicity: MultiplicityType): void { + protected registerInferenceRules( + _typeDetails: MultiplicityTypeDetails, + _typeWithMultiplicity: MultiplicityType, + ): void { // TODO } - calculateIdentifier(typeDetails: MultiplicityTypeDetails): string { + calculateIdentifier( + typeDetails: MultiplicityTypeDetails, + ): string { return `${typeDetails.constrainedType.getIdentifier()}${this.printRange(typeDetails.lowerBound, typeDetails.upperBound)}`; } @@ -90,7 +111,10 @@ export class MultiplicityKind implements Kind { } printRange(lowerBound: number, upperBound: number): string { - if (lowerBound === upperBound || (lowerBound === 0 && upperBound === MULTIPLICITY_UNLIMITED)) { + if ( + lowerBound === upperBound || + (lowerBound === 0 && upperBound === MULTIPLICITY_UNLIMITED) + ) { // [2..2] => [2], [0..*] => [*] return `[${this.printBound(upperBound)}]`; } else { @@ -99,7 +123,9 @@ export class MultiplicityKind implements Kind { } } protected printBound(bound: number): string { - return bound === MULTIPLICITY_UNLIMITED ? this.options.symbolForUnlimited : `${bound}`; + return bound === MULTIPLICITY_UNLIMITED + ? this.options.symbolForUnlimited + : `${bound}`; } isBoundGreaterEquals(leftBound: number, rightBound: number): boolean { @@ -111,9 +137,10 @@ export class MultiplicityKind implements Kind { } return leftBound >= rightBound; } - } -export function isMultiplicityKind(kind: unknown): kind is MultiplicityKind { +export function isMultiplicityKind( + kind: unknown, +): kind is MultiplicityKind { return isKind(kind) && kind.$name === MultiplicityKindName; } diff --git a/packages/typir/src/kinds/multiplicity/multiplicity-type.ts b/packages/typir/src/kinds/multiplicity/multiplicity-type.ts index 197ebde7..bc6283d7 100644 --- a/packages/typir/src/kinds/multiplicity/multiplicity-type.ts +++ b/packages/typir/src/kinds/multiplicity/multiplicity-type.ts @@ -2,14 +2,21 @@ * Copyright 2024 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ + ******************************************************************************/ -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { isSubTypeProblem } from '../../services/subtype.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { checkValueForConflict, createKindConflict } from '../../utils/utils-type-comparison.js'; -import { isMultiplicityKind, MultiplicityKind, MultiplicityTypeDetails } from './multiplicity-kind.js'; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { isSubTypeProblem } from "../../services/subtype.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { + checkValueForConflict, + createKindConflict, +} from "../../utils/utils-type-comparison.js"; +import { + isMultiplicityKind, + MultiplicityKind, + MultiplicityTypeDetails, +} from "./multiplicity-kind.js"; export class MultiplicityType extends Type { override readonly kind: MultiplicityKind; @@ -17,7 +24,11 @@ export class MultiplicityType extends Type { readonly lowerBound: number; readonly upperBound: number; - constructor(kind: MultiplicityKind, identifier: string, typeDetails: MultiplicityTypeDetails) { + constructor( + kind: MultiplicityKind, + identifier: string, + typeDetails: MultiplicityTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; this.constrainedType = typeDetails.constrainedType; @@ -38,31 +49,70 @@ export class MultiplicityType extends Type { if (isMultiplicityKind(otherType)) { const conflicts: TypirProblem[] = []; // check the multiplicities - conflicts.push(...checkValueForConflict(this.getLowerBound(), this.getLowerBound(), 'lower bound')); - conflicts.push(...checkValueForConflict(this.getUpperBound(), this.getUpperBound(), 'upper bound')); + conflicts.push( + ...checkValueForConflict( + this.getLowerBound(), + this.getLowerBound(), + "lower bound", + ), + ); + conflicts.push( + ...checkValueForConflict( + this.getUpperBound(), + this.getUpperBound(), + "upper bound", + ), + ); // check the constrained type - const constrainedTypeConflict = this.kind.services.Equality.getTypeEqualityProblem(this.getConstrainedType(), this.getConstrainedType()); + const constrainedTypeConflict = + this.kind.services.Equality.getTypeEqualityProblem( + this.getConstrainedType(), + this.getConstrainedType(), + ); if (constrainedTypeConflict !== undefined) { conflicts.push(constrainedTypeConflict); } return conflicts; } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - protected analyzeSubTypeProblems(subType: MultiplicityType, superType: MultiplicityType): TypirProblem[] { + protected analyzeSubTypeProblems( + subType: MultiplicityType, + superType: MultiplicityType, + ): TypirProblem[] { const conflicts: TypirProblem[] = []; // check the multiplicities - conflicts.push(...checkValueForConflict(subType.getLowerBound(), superType.getLowerBound(), 'lower bound', this.kind.isBoundGreaterEquals)); - conflicts.push(...checkValueForConflict(subType.getUpperBound(), superType.getUpperBound(), 'upper bound', this.kind.isBoundGreaterEquals)); + conflicts.push( + ...checkValueForConflict( + subType.getLowerBound(), + superType.getLowerBound(), + "lower bound", + this.kind.isBoundGreaterEquals, + ), + ); + conflicts.push( + ...checkValueForConflict( + subType.getUpperBound(), + superType.getUpperBound(), + "upper bound", + this.kind.isBoundGreaterEquals, + ), + ); // check the constrained type - const constrainedTypeConflict = this.kind.services.Subtype.getSubTypeResult(subType.getConstrainedType(), superType.getConstrainedType()); + const constrainedTypeConflict = + this.kind.services.Subtype.getSubTypeResult( + subType.getConstrainedType(), + superType.getConstrainedType(), + ); if (isSubTypeProblem(constrainedTypeConflict)) { conflicts.push(constrainedTypeConflict); } diff --git a/packages/typir/src/kinds/primitive/primitive-kind.ts b/packages/typir/src/kinds/primitive/primitive-kind.ts index 67161ecb..4aa6055a 100644 --- a/packages/typir/src/kinds/primitive/primitive-kind.ts +++ b/packages/typir/src/kinds/primitive/primitive-kind.ts @@ -4,81 +4,116 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { InferCurrentTypeRule, registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; -import { assertTrue } from '../../utils/utils.js'; -import { isKind, Kind } from '../kind.js'; -import { PrimitiveType } from './primitive-type.js'; +import { TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { + InferCurrentTypeRule, + registerInferCurrentTypeRules, +} from "../../utils/utils-definitions.js"; +import { assertTrue } from "../../utils/utils.js"; +import { isKind, Kind } from "../kind.js"; +import { PrimitiveType } from "./primitive-type.js"; export interface PrimitiveKindOptions { // empty for now } -export interface PrimitiveTypeDetails extends TypeDetails { +export interface PrimitiveTypeDetails + extends TypeDetails { primitiveName: string; } -interface CreatePrimitiveTypeDetails extends PrimitiveTypeDetails { +interface CreatePrimitiveTypeDetails + extends PrimitiveTypeDetails { inferenceRules: Array>; } -export const PrimitiveKindName = 'PrimitiveKind'; +export const PrimitiveKindName = "PrimitiveKind"; export interface PrimitiveFactoryService { - create(typeDetails: PrimitiveTypeDetails): PrimitiveConfigurationChain; - get(typeDetails: PrimitiveTypeDetails): PrimitiveType | undefined; + create( + typeDetails: PrimitiveTypeDetails, + ): PrimitiveConfigurationChain; + get( + typeDetails: PrimitiveTypeDetails, + ): PrimitiveType | undefined; } export interface PrimitiveConfigurationChain { - inferenceRule(rule: InferCurrentTypeRule): PrimitiveConfigurationChain; + inferenceRule( + rule: InferCurrentTypeRule, + ): PrimitiveConfigurationChain; finish(): PrimitiveType; } -export class PrimitiveKind implements Kind, PrimitiveFactoryService { - readonly $name: 'PrimitiveKind'; +export class PrimitiveKind + implements Kind, PrimitiveFactoryService +{ + readonly $name: "PrimitiveKind"; readonly services: TypirServices; readonly options: PrimitiveKindOptions; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = PrimitiveKindName; this.services = services; this.services.infrastructure.Kinds.register(this); this.options = this.collectOptions(options); } - protected collectOptions(options?: Partial): PrimitiveKindOptions { + protected collectOptions( + options?: Partial, + ): PrimitiveKindOptions { return { ...options, }; } - get(typeDetails: PrimitiveTypeDetails): PrimitiveType | undefined { + get( + typeDetails: PrimitiveTypeDetails, + ): PrimitiveType | undefined { const key = this.calculateIdentifier(typeDetails); return this.services.infrastructure.Graph.getType(key) as PrimitiveType; } - create(typeDetails: PrimitiveTypeDetails): PrimitiveConfigurationChain { + create( + typeDetails: PrimitiveTypeDetails, + ): PrimitiveConfigurationChain { assertTrue(this.get(typeDetails) === undefined); // ensure that the type is not created twice - return new PrimitiveConfigurationChainImpl(this.services, this, typeDetails); + return new PrimitiveConfigurationChainImpl( + this.services, + this, + typeDetails, + ); } - calculateIdentifier(typeDetails: PrimitiveTypeDetails): string { + calculateIdentifier( + typeDetails: PrimitiveTypeDetails, + ): string { return typeDetails.primitiveName; } } -export function isPrimitiveKind(kind: unknown): kind is PrimitiveKind { +export function isPrimitiveKind( + kind: unknown, +): kind is PrimitiveKind { return isKind(kind) && kind.$name === PrimitiveKindName; } - -class PrimitiveConfigurationChainImpl implements PrimitiveConfigurationChain { +class PrimitiveConfigurationChainImpl + implements PrimitiveConfigurationChain +{ protected readonly services: TypirServices; protected readonly kind: PrimitiveKind; protected readonly typeDetails: CreatePrimitiveTypeDetails; - constructor(services: TypirServices, kind: PrimitiveKind, typeDetails: PrimitiveTypeDetails) { + constructor( + services: TypirServices, + kind: PrimitiveKind, + typeDetails: PrimitiveTypeDetails, + ) { this.services = services; this.kind = kind; this.typeDetails = { @@ -87,18 +122,33 @@ class PrimitiveConfigurationChainImpl implements PrimitiveConfigur }; } - inferenceRule(rule: InferCurrentTypeRule): PrimitiveConfigurationChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferCurrentTypeRule); + inferenceRule( + rule: InferCurrentTypeRule, + ): PrimitiveConfigurationChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferCurrentTypeRule< + PrimitiveType, + LanguageType + >, + ); return this; } finish(): PrimitiveType { // create the primitive type - const currentPrimitiveType = new PrimitiveType(this.kind as PrimitiveKind, this.kind.calculateIdentifier(this.typeDetails), this.typeDetails); + const currentPrimitiveType = new PrimitiveType( + this.kind as PrimitiveKind, + this.kind.calculateIdentifier(this.typeDetails), + this.typeDetails, + ); this.services.infrastructure.Graph.addNode(currentPrimitiveType); // register the inference rules - registerInferCurrentTypeRules(this.typeDetails.inferenceRules, currentPrimitiveType, this.services); + registerInferCurrentTypeRules( + this.typeDetails.inferenceRules, + currentPrimitiveType, + this.services, + ); return currentPrimitiveType; } diff --git a/packages/typir/src/kinds/primitive/primitive-type.ts b/packages/typir/src/kinds/primitive/primitive-type.ts index 7c2d99b1..d9bc4f19 100644 --- a/packages/typir/src/kinds/primitive/primitive-type.ts +++ b/packages/typir/src/kinds/primitive/primitive-type.ts @@ -4,16 +4,27 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { checkValueForConflict, createKindConflict } from '../../utils/utils-type-comparison.js'; -import { isPrimitiveKind, PrimitiveKind, PrimitiveTypeDetails } from './primitive-kind.js'; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { + checkValueForConflict, + createKindConflict, +} from "../../utils/utils-type-comparison.js"; +import { + isPrimitiveKind, + PrimitiveKind, + PrimitiveTypeDetails, +} from "./primitive-kind.js"; export class PrimitiveType extends Type { override readonly kind: PrimitiveKind; - constructor(kind: PrimitiveKind, identifier: string, typeDetails: PrimitiveTypeDetails) { + constructor( + kind: PrimitiveKind, + identifier: string, + typeDetails: PrimitiveTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; this.defineTheInitializationProcessOfThisType({}); // no preconditions @@ -29,21 +40,29 @@ export class PrimitiveType extends Type { override analyzeTypeEqualityProblems(otherType: Type): TypirProblem[] { if (isPrimitiveType(otherType)) { - return checkValueForConflict(this.getIdentifier(), otherType.getIdentifier(), 'name'); + return checkValueForConflict( + this.getIdentifier(), + otherType.getIdentifier(), + "name", + ); } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - protected analyzeSubTypeProblems(subType: PrimitiveType, superType: PrimitiveType): TypirProblem[] { + protected analyzeSubTypeProblems( + subType: PrimitiveType, + superType: PrimitiveType, + ): TypirProblem[] { return subType.analyzeTypeEqualityProblems(superType); } - } export function isPrimitiveType(type: unknown): type is PrimitiveType { diff --git a/packages/typir/src/kinds/top/top-kind.ts b/packages/typir/src/kinds/top/top-kind.ts index c99dffc5..49b66625 100644 --- a/packages/typir/src/kinds/top/top-kind.ts +++ b/packages/typir/src/kinds/top/top-kind.ts @@ -4,17 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from '../../graph/type-node.js'; -import { TypirServices } from '../../typir.js'; -import { InferCurrentTypeRule, registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; -import { assertTrue } from '../../utils/utils.js'; -import { isKind, Kind } from '../kind.js'; -import { TopType } from './top-type.js'; - -export interface TopTypeDetails extends TypeDetails { +import { TypeDetails } from "../../graph/type-node.js"; +import { TypirServices } from "../../typir.js"; +import { + InferCurrentTypeRule, + registerInferCurrentTypeRules, +} from "../../utils/utils-definitions.js"; +import { assertTrue } from "../../utils/utils.js"; +import { isKind, Kind } from "../kind.js"; +import { TopType } from "./top-type.js"; + +export interface TopTypeDetails + extends TypeDetails { // empty } -interface CreateTopTypeDetails extends TopTypeDetails { +interface CreateTopTypeDetails + extends TopTypeDetails { inferenceRules: Array>; } @@ -22,36 +27,47 @@ export interface TopKindOptions { name: string; } -export const TopKindName = 'TopKind'; +export const TopKindName = "TopKind"; export interface TopFactoryService { - create(typeDetails: TopTypeDetails): TopConfigurationChain; + create( + typeDetails: TopTypeDetails, + ): TopConfigurationChain; get(typeDetails: TopTypeDetails): TopType | undefined; } export interface TopConfigurationChain { - inferenceRule(rule: InferCurrentTypeRule): TopConfigurationChain; + inferenceRule( + rule: InferCurrentTypeRule, + ): TopConfigurationChain; finish(): TopType; } -export class TopKind implements Kind, TopFactoryService { - readonly $name: 'TopKind'; +export class TopKind + implements Kind, TopFactoryService +{ + readonly $name: "TopKind"; readonly services: TypirServices; readonly options: Readonly; - constructor(services: TypirServices, options?: Partial) { + constructor( + services: TypirServices, + options?: Partial, + ) { this.$name = TopKindName; this.services = services; this.services.infrastructure.Kinds.register(this); this.options = this.collectOptions(options); } - protected collectOptions(options?: Partial): TopKindOptions { + protected collectOptions( + options?: Partial, + ): TopKindOptions { return { // the default values: - name: 'any', + name: "any", // the actually overriden values: - ...options + ...options, }; } @@ -60,7 +76,9 @@ export class TopKind implements Kind, TopFactoryService): TopConfigurationChain { + create( + typeDetails: TopTypeDetails, + ): TopConfigurationChain { assertTrue(this.get(typeDetails) === undefined); // ensure that the type is not created twice return new TopConfigurationChainImpl(this.services, this, typeDetails); } @@ -68,20 +86,26 @@ export class TopKind implements Kind, TopFactoryService): string { return this.options.name; } - } -export function isTopKind(kind: unknown): kind is TopKind { +export function isTopKind( + kind: unknown, +): kind is TopKind { return isKind(kind) && kind.$name === TopKindName; } - -class TopConfigurationChainImpl implements TopConfigurationChain { +class TopConfigurationChainImpl + implements TopConfigurationChain +{ protected readonly services: TypirServices; protected readonly kind: TopKind; protected readonly typeDetails: CreateTopTypeDetails; - constructor(services: TypirServices, kind: TopKind, typeDetails: TopTypeDetails) { + constructor( + services: TypirServices, + kind: TopKind, + typeDetails: TopTypeDetails, + ) { this.services = services; this.kind = kind; this.typeDetails = { @@ -90,16 +114,28 @@ class TopConfigurationChainImpl implements TopConfigurationChain(rule: InferCurrentTypeRule): TopConfigurationChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferCurrentTypeRule); + inferenceRule( + rule: InferCurrentTypeRule, + ): TopConfigurationChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferCurrentTypeRule, + ); return this; } finish(): TopType { - const topType = new TopType(this.kind as TopKind, this.kind.calculateIdentifier(this.typeDetails), this.typeDetails); + const topType = new TopType( + this.kind as TopKind, + this.kind.calculateIdentifier(this.typeDetails), + this.typeDetails, + ); this.services.infrastructure.Graph.addNode(topType); - registerInferCurrentTypeRules(this.typeDetails.inferenceRules, topType, this.services); + registerInferCurrentTypeRules( + this.typeDetails.inferenceRules, + topType, + this.services, + ); return topType; } diff --git a/packages/typir/src/kinds/top/top-type.ts b/packages/typir/src/kinds/top/top-type.ts index 96b2bf29..366e8bbf 100644 --- a/packages/typir/src/kinds/top/top-type.ts +++ b/packages/typir/src/kinds/top/top-type.ts @@ -4,24 +4,28 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; -import { isType, Type } from '../../graph/type-node.js'; -import { TypeEqualityProblem } from '../../services/equality.js'; -import { TypirProblem } from '../../utils/utils-definitions.js'; -import { createKindConflict } from '../../utils/utils-type-comparison.js'; -import { isTopKind, TopKind, TopTypeDetails } from './top-kind.js'; +import { TypeGraphListener } from "../../graph/type-graph.js"; +import { isType, Type } from "../../graph/type-node.js"; +import { TypeEqualityProblem } from "../../services/equality.js"; +import { TypirProblem } from "../../utils/utils-definitions.js"; +import { createKindConflict } from "../../utils/utils-type-comparison.js"; +import { isTopKind, TopKind, TopTypeDetails } from "./top-kind.js"; export class TopType extends Type implements TypeGraphListener { override readonly kind: TopKind; - constructor(kind: TopKind, identifier: string, typeDetails: TopTypeDetails) { + constructor( + kind: TopKind, + identifier: string, + typeDetails: TopTypeDetails, + ) { super(identifier, typeDetails); this.kind = kind; this.defineTheInitializationProcessOfThisType({}); // no preconditions // ensure, that all (other) types are a sub-type of this Top type: const graph = kind.services.infrastructure.Graph; - graph.getAllRegisteredTypes().forEach(t => this.markAsSubType(t)); // the already existing types + graph.getAllRegisteredTypes().forEach((t) => this.markAsSubType(t)); // the already existing types graph.addListener(this); // all upcomping types } @@ -31,7 +35,9 @@ export class TopType extends Type implements TypeGraphListener { protected markAsSubType(type: Type): void { if (type !== this) { - this.kind.services.Subtype.markAsSubType(type, this, { checkForCycles: false }); + this.kind.services.Subtype.markAsSubType(type, this, { + checkForCycles: false, + }); } } @@ -51,15 +57,16 @@ export class TopType extends Type implements TypeGraphListener { if (isTopType(otherType)) { return []; } else { - return [{ - $problem: TypeEqualityProblem, - type1: this, - type2: otherType, - subProblems: [createKindConflict(otherType, this)], - }]; + return [ + { + $problem: TypeEqualityProblem, + type1: this, + type2: otherType, + subProblems: [createKindConflict(otherType, this)], + }, + ]; } } - } export function isTopType(type: unknown): type is TopType { diff --git a/packages/typir/src/services/assignability.ts b/packages/typir/src/services/assignability.ts index 0268d0b4..b0ad68d4 100644 --- a/packages/typir/src/services/assignability.ts +++ b/packages/typir/src/services/assignability.ts @@ -4,57 +4,74 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from '../graph/graph-algorithms.js'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { TypirProblem } from '../utils/utils-definitions.js'; -import { ConversionEdge, isConversionEdge, TypeConversion } from './conversion.js'; -import { TypeEquality } from './equality.js'; -import { SubType, SubTypeEdge } from './subtype.js'; +import { GraphAlgorithms } from "../graph/graph-algorithms.js"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { TypirProblem } from "../utils/utils-definitions.js"; +import { + ConversionEdge, + isConversionEdge, + TypeConversion, +} from "./conversion.js"; +import { TypeEquality } from "./equality.js"; +import { SubType, SubTypeEdge } from "./subtype.js"; export interface AssignabilityProblem extends TypirProblem { - $problem: 'AssignabilityProblem'; - $result: 'AssignabilityResult'; + $problem: "AssignabilityProblem"; + $result: "AssignabilityResult"; source: Type; target: Type; result: false; subProblems: TypirProblem[]; } -export const AssignabilityProblem = 'AssignabilityProblem'; -export function isAssignabilityProblem(problem: unknown): problem is AssignabilityProblem { +export const AssignabilityProblem = "AssignabilityProblem"; +export function isAssignabilityProblem( + problem: unknown, +): problem is AssignabilityProblem { return isAssignabilityResult(problem) && problem.result === false; } export interface AssignabilitySuccess { - $result: 'AssignabilityResult'; + $result: "AssignabilityResult"; source: Type; target: Type; result: true; path: Array; } -export function isAssignabilitySuccess(success: unknown): success is AssignabilitySuccess { +export function isAssignabilitySuccess( + success: unknown, +): success is AssignabilitySuccess { return isAssignabilityResult(success) && success.result === true; } export type AssignabilityResult = AssignabilitySuccess | AssignabilityProblem; -export const AssignabilityResult = 'AssignabilityResult'; -export function isAssignabilityResult(result: unknown): result is AssignabilityResult { - return typeof result === 'object' && result !== null && ((result as AssignabilityResult).$result === AssignabilityResult); +export const AssignabilityResult = "AssignabilityResult"; +export function isAssignabilityResult( + result: unknown, +): result is AssignabilityResult { + return ( + typeof result === "object" && + result !== null && + (result as AssignabilityResult).$result === AssignabilityResult + ); } - export interface TypeAssignability { // target := source; isAssignable(source: Type, target: Type): boolean; - getAssignabilityProblem(source: Type, target: Type): AssignabilityProblem | undefined; + getAssignabilityProblem( + source: Type, + target: Type, + ): AssignabilityProblem | undefined; getAssignabilityResult(source: Type, target: Type): AssignabilityResult; } - /** * This implementation for assignability checks step-by-step (1) equality, (2) implicit conversion, and (3) sub-type relationships of the source and target type. */ -export class DefaultTypeAssignability implements TypeAssignability { +export class DefaultTypeAssignability + implements TypeAssignability +{ protected readonly conversion: TypeConversion; protected readonly subtype: SubType; protected readonly equality: TypeEquality; @@ -68,10 +85,17 @@ export class DefaultTypeAssignability implements TypeAssignability } isAssignable(source: Type, target: Type): boolean { - return isAssignabilityProblem(this.getAssignabilityProblem(source, target)) === false; + return ( + isAssignabilityProblem( + this.getAssignabilityProblem(source, target), + ) === false + ); } - getAssignabilityProblem(source: Type, target: Type): AssignabilityProblem | undefined { + getAssignabilityProblem( + source: Type, + target: Type, + ): AssignabilityProblem | undefined { const result = this.getAssignabilityResult(source, target); return isAssignabilityProblem(result) ? result : undefined; } @@ -89,8 +113,15 @@ export class DefaultTypeAssignability implements TypeAssignability } // 2. any path of implicit conversion and sub-type relationships - const path = this.algorithms.getEdgePath(source, target, [ConversionEdge, SubTypeEdge], - edge => isConversionEdge(edge) ? edge.mode === 'IMPLICIT_EXPLICIT' : true); // no explicit conversion + const path = this.algorithms.getEdgePath( + source, + target, + [ConversionEdge, SubTypeEdge], + (edge) => + isConversionEdge(edge) + ? edge.mode === "IMPLICIT_EXPLICIT" + : true, + ); // no explicit conversion if (path.length >= 1) { return { $result: AssignabilityResult, diff --git a/packages/typir/src/services/caching.ts b/packages/typir/src/services/caching.ts index 09a818d7..88b91b6b 100644 --- a/packages/typir/src/services/caching.ts +++ b/packages/typir/src/services/caching.ts @@ -4,60 +4,104 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeEdge } from '../graph/type-edge.js'; -import { TypeGraph } from '../graph/type-graph.js'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { assertTrue } from '../utils/utils.js'; +import { TypeEdge } from "../graph/type-edge.js"; +import { TypeGraph } from "../graph/type-graph.js"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { assertTrue } from "../utils/utils.js"; /** * Caches relationships between types. */ export interface TypeRelationshipCaching { - getRelationshipUnidirectional(from: Type, to: Type, $relation: T['$relation']): T | undefined; - getRelationshipBidirectional(from: Type, to: Type, $relation: T['$relation']): T | undefined; - - setOrUpdateUnidirectionalRelationship(edgeToCache: T, edgeCaching: EdgeCachingInformation): T | undefined; - setOrUpdateBidirectionalRelationship(edgeToCache: T, edgeCaching: EdgeCachingInformation): T | undefined; + getRelationshipUnidirectional( + from: Type, + to: Type, + $relation: T["$relation"], + ): T | undefined; + getRelationshipBidirectional( + from: Type, + to: Type, + $relation: T["$relation"], + ): T | undefined; + + setOrUpdateUnidirectionalRelationship( + edgeToCache: T, + edgeCaching: EdgeCachingInformation, + ): T | undefined; + setOrUpdateBidirectionalRelationship( + edgeToCache: T, + edgeCaching: EdgeCachingInformation, + ): T | undefined; } export type EdgeCachingInformation = /** The analysis, whether the current relationship holds, is still ongoing. */ - 'PENDING' | + | "PENDING" /** It is unknown, whether the current relationship holds */ - 'UNKNOWN' | + | "UNKNOWN" /** The current relationship exists. */ - 'LINK_EXISTS' | + | "LINK_EXISTS" /** The current relationship does not exist. */ - 'NO_LINK'; + | "NO_LINK"; -export class DefaultTypeRelationshipCaching implements TypeRelationshipCaching { +export class DefaultTypeRelationshipCaching + implements TypeRelationshipCaching +{ protected readonly graph: TypeGraph; constructor(services: TypirServices) { this.graph = services.infrastructure.Graph; } - getRelationshipUnidirectional(from: Type, to: Type, $relation: T['$relation']): T | undefined { - return from.getOutgoingEdges($relation).find(edge => edge.to === to); + getRelationshipUnidirectional( + from: Type, + to: Type, + $relation: T["$relation"], + ): T | undefined { + return from + .getOutgoingEdges($relation) + .find((edge) => edge.to === to); } - getRelationshipBidirectional(from: Type, to: Type, $relation: T['$relation']): T | undefined { + getRelationshipBidirectional( + from: Type, + to: Type, + $relation: T["$relation"], + ): T | undefined { // for bidirectional edges, check outgoing and incoming edges, since the graph contains only a single edge! - return from.getEdges($relation).find(edge => edge.to === to); + return from.getEdges($relation).find((edge) => edge.to === to); } - setOrUpdateUnidirectionalRelationship(edgeToCache: T, edgeCaching: EdgeCachingInformation): T | undefined { + setOrUpdateUnidirectionalRelationship( + edgeToCache: T, + edgeCaching: EdgeCachingInformation, + ): T | undefined { return this.setOrUpdateRelationship(edgeToCache, edgeCaching, false); } - setOrUpdateBidirectionalRelationship(edgeToCache: T, edgeCaching: EdgeCachingInformation): T | undefined { + setOrUpdateBidirectionalRelationship( + edgeToCache: T, + edgeCaching: EdgeCachingInformation, + ): T | undefined { return this.setOrUpdateRelationship(edgeToCache, edgeCaching, true); } - protected setOrUpdateRelationship(edgeToCache: T, edgeCaching: EdgeCachingInformation, bidirectional: boolean): T | undefined { + protected setOrUpdateRelationship( + edgeToCache: T, + edgeCaching: EdgeCachingInformation, + bidirectional: boolean, + ): T | undefined { // identify the edge to store the value const edge: T | undefined = bidirectional - ? this.getRelationshipBidirectional(edgeToCache.from, edgeToCache.to, edgeToCache.$relation) - : this.getRelationshipUnidirectional(edgeToCache.from, edgeToCache.to, edgeToCache.$relation); + ? this.getRelationshipBidirectional( + edgeToCache.from, + edgeToCache.to, + edgeToCache.$relation, + ) + : this.getRelationshipUnidirectional( + edgeToCache.from, + edgeToCache.to, + edgeToCache.$relation, + ); // don't cache some values (but ensure, that PENDING is overridden/updated!) => un-set the relationship if (this.storeCachingInformation(edgeCaching) === false) { @@ -82,7 +126,12 @@ export class DefaultTypeRelationshipCaching implements TypeRelatio assertTrue(edge.$relation === edgeToCache.$relation); // update data of specific edges! // Object.assign throws an error for readonly properties => it cannot be used here! - const propertiesToIgnore: Array = ['from', 'to', '$relation', 'cachingInformation']; + const propertiesToIgnore: Array = [ + "from", + "to", + "$relation", + "cachingInformation", + ]; const keys = Object.keys(edgeToCache) as Array; for (const v of keys) { if (propertiesToIgnore.includes(v as keyof TypeEdge)) { @@ -95,28 +144,31 @@ export class DefaultTypeRelationshipCaching implements TypeRelatio } /** Override this function to store more or less relationships in the type graph. */ - protected storeCachingInformation(value: EdgeCachingInformation | undefined): boolean { - return value === 'PENDING' || value === 'LINK_EXISTS'; + protected storeCachingInformation( + value: EdgeCachingInformation | undefined, + ): boolean { + return value === "PENDING" || value === "LINK_EXISTS"; } } - /** * Language node-to-Type caching for type inference. */ export interface LanguageNodeInferenceCaching { cacheSet(languageNode: unknown, type: Type): void; cacheGet(languageNode: unknown): Type | undefined; - cacheClear(): void + cacheClear(): void; pendingSet(languageNode: unknown): void; pendingClear(languageNode: unknown): void; pendingGet(languageNode: unknown): boolean; } -export type CachePending = 'CACHE_PENDING'; -export const CachePending = 'CACHE_PENDING'; +export type CachePending = "CACHE_PENDING"; +export const CachePending = "CACHE_PENDING"; -export class DefaultLanguageNodeInferenceCaching implements LanguageNodeInferenceCaching { +export class DefaultLanguageNodeInferenceCaching + implements LanguageNodeInferenceCaching +{ protected cache: Map; constructor() { @@ -136,7 +188,7 @@ export class DefaultLanguageNodeInferenceCaching implements LanguageNodeInferenc if (this.pendingGet(languageNode)) { return undefined; } else { - return this.cache.get(languageNode) as (Type | undefined); + return this.cache.get(languageNode) as Type | undefined; } } @@ -157,6 +209,9 @@ export class DefaultLanguageNodeInferenceCaching implements LanguageNodeInferenc } pendingGet(languageNode: unknown): boolean { - return this.cache.has(languageNode) && this.cache.get(languageNode) === CachePending; + return ( + this.cache.has(languageNode) && + this.cache.get(languageNode) === CachePending + ); } } diff --git a/packages/typir/src/services/conversion.ts b/packages/typir/src/services/conversion.ts index 98310528..11add1ef 100644 --- a/packages/typir/src/services/conversion.ts +++ b/packages/typir/src/services/conversion.ts @@ -4,12 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from '../graph/graph-algorithms.js'; -import { isTypeEdge, TypeEdge } from '../graph/type-edge.js'; -import { TypeGraph } from '../graph/type-graph.js'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { TypeEquality } from './equality.js'; +import { GraphAlgorithms } from "../graph/graph-algorithms.js"; +import { isTypeEdge, TypeEdge } from "../graph/type-edge.js"; +import { TypeGraph } from "../graph/type-graph.js"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { TypeEquality } from "./equality.js"; /** * Describes the possible conversion modes. @@ -26,15 +26,15 @@ import { TypeEquality } from './equality.js'; */ export type ConversionModeForSpecification = /** The conversion is implicitly possible. In this case, the explicit conversion is possible as well (IMPLICIT => EXPLICIT). */ - 'IMPLICIT_EXPLICIT' | + | "IMPLICIT_EXPLICIT" /** The conversion is only explicitly possible */ - 'EXPLICIT'; + | "EXPLICIT"; export type ConversionMode = - ConversionModeForSpecification | + | ConversionModeForSpecification /** no conversion possible at all (this is the default mode) */ - 'NONE' | + | "NONE" /** a type is always self-convertible to itself (implicitly or explicitly), in this case no conversion is necessary */ - 'SELF'; + | "SELF"; /** * Manages conversions between different types. @@ -49,7 +49,11 @@ export interface TypeConversion { * @param mode the desired conversion relationship between the two given types * @throws an error, if a cycle was introduced */ - markAsConvertible(from: Type, to: Type, mode: ConversionModeForSpecification): void; + markAsConvertible( + from: Type, + to: Type, + mode: ConversionModeForSpecification, + ): void; /** * Identifies the existing conversion relationship between two given types. @@ -101,7 +105,10 @@ export interface TypeConversion { * @param mode only conversion rules with the given conversion mode are considered * @returns the set of recursively reachable types for conversion ("conversion targets") */ - getConvertibleTo(from: Type, mode: ConversionModeForSpecification): Set; + getConvertibleTo( + from: Type, + mode: ConversionModeForSpecification, + ): Set; } /** @@ -120,7 +127,11 @@ export class DefaultTypeConversion implements TypeConversion { this.algorithms = services.infrastructure.GraphAlgorithms; } - markAsConvertible(from: Type, to: Type, mode: ConversionModeForSpecification): void { + markAsConvertible( + from: Type, + to: Type, + mode: ConversionModeForSpecification, + ): void { let edge = this.getConversionEdge(from, to); if (!edge) { // create a missing edge (with the desired mode) @@ -128,7 +139,7 @@ export class DefaultTypeConversion implements TypeConversion { $relation: ConversionEdge, from, to, - cachingInformation: 'LINK_EXISTS', + cachingInformation: "LINK_EXISTS", mode, }; this.graph.addEdge(edge); @@ -137,20 +148,22 @@ export class DefaultTypeConversion implements TypeConversion { edge.mode = mode; } - if (mode === 'IMPLICIT_EXPLICIT') { + if (mode === "IMPLICIT_EXPLICIT") { /* check that the new edges did not introduce cycles * if it did, the from node will be reachable via a cycle path */ const hasIntroducedCycle = this.existsEdgePath(from, from, mode); if (hasIntroducedCycle) { - throw new Error(`Adding the conversion from ${from.getIdentifier()} to ${to.getIdentifier()} with mode ${mode} has introduced a cycle in the type graph.`); + throw new Error( + `Adding the conversion from ${from.getIdentifier()} to ${to.getIdentifier()} with mode ${mode} has introduced a cycle in the type graph.`, + ); } } } protected isTransitive(mode: ConversionModeForSpecification): boolean { // by default, only IMPLICIT is transitive! - return mode === 'IMPLICIT_EXPLICIT'; + return mode === "IMPLICIT_EXPLICIT"; } getConversion(from: Type, to: Type): ConversionMode { @@ -162,69 +175,107 @@ export class DefaultTypeConversion implements TypeConversion { // special case: if both types are equal, no conversion is needed (often this check is quite fast) if (this.equality.areTypesEqual(from, to)) { - return 'SELF'; + return "SELF"; } // check whether there is a transitive relationship (in general, these checks are expensive) - if (this.isTransitive('EXPLICIT') && this.isTransitivelyConvertable(from, to, 'EXPLICIT')) { - return 'EXPLICIT'; + if ( + this.isTransitive("EXPLICIT") && + this.isTransitivelyConvertable(from, to, "EXPLICIT") + ) { + return "EXPLICIT"; } - if (this.isTransitive('IMPLICIT_EXPLICIT') && this.isTransitivelyConvertable(from, to, 'IMPLICIT_EXPLICIT')) { - return 'IMPLICIT_EXPLICIT'; + if ( + this.isTransitive("IMPLICIT_EXPLICIT") && + this.isTransitivelyConvertable(from, to, "IMPLICIT_EXPLICIT") + ) { + return "IMPLICIT_EXPLICIT"; } // the default case - return 'NONE'; + return "NONE"; } - protected collectReachableTypes(from: Type, mode: ConversionModeForSpecification): Set { - return this.algorithms.collectReachableTypes(from, [ConversionEdge], edge => (edge as ConversionEdge).mode === mode); + protected collectReachableTypes( + from: Type, + mode: ConversionModeForSpecification, + ): Set { + return this.algorithms.collectReachableTypes( + from, + [ConversionEdge], + (edge) => (edge as ConversionEdge).mode === mode, + ); } - protected existsEdgePath(from: Type, to: Type, mode: ConversionModeForSpecification): boolean { - return this.algorithms.existsEdgePath(from, to, [ConversionEdge], edge => (edge as ConversionEdge).mode === mode); + protected existsEdgePath( + from: Type, + to: Type, + mode: ConversionModeForSpecification, + ): boolean { + return this.algorithms.existsEdgePath( + from, + to, + [ConversionEdge], + (edge) => (edge as ConversionEdge).mode === mode, + ); } - protected isTransitivelyConvertable(from: Type, to: Type, mode: ConversionModeForSpecification): boolean { + protected isTransitivelyConvertable( + from: Type, + to: Type, + mode: ConversionModeForSpecification, + ): boolean { if (from === to) { return true; } else { - return(this.existsEdgePath(from, to, mode)); + return this.existsEdgePath(from, to, mode); } } isImplicitExplicitConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === 'IMPLICIT_EXPLICIT'; + return this.getConversion(from, to) === "IMPLICIT_EXPLICIT"; } isExplicitConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === 'EXPLICIT'; + return this.getConversion(from, to) === "EXPLICIT"; } isNoneConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === 'NONE'; + return this.getConversion(from, to) === "NONE"; } isSelfConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === 'SELF'; + return this.getConversion(from, to) === "SELF"; } isConvertible(from: Type, to: Type): boolean { const currentMode = this.getConversion(from, to); - return currentMode === 'IMPLICIT_EXPLICIT' || currentMode === 'EXPLICIT' || currentMode === 'SELF'; + return ( + currentMode === "IMPLICIT_EXPLICIT" || + currentMode === "EXPLICIT" || + currentMode === "SELF" + ); } - protected getConversionEdge(from: Type, to: Type): ConversionEdge | undefined { - return from.getOutgoingEdges(ConversionEdge).find(edge => edge.to === to); + protected getConversionEdge( + from: Type, + to: Type, + ): ConversionEdge | undefined { + return from + .getOutgoingEdges(ConversionEdge) + .find((edge) => edge.to === to); } - getConvertibleTo(from: Type, mode: ConversionModeForSpecification): Set { + getConvertibleTo( + from: Type, + mode: ConversionModeForSpecification, + ): Set { return this.collectReachableTypes(from, mode); } } export interface ConversionEdge extends TypeEdge { - readonly $relation: 'ConversionEdge'; + readonly $relation: "ConversionEdge"; mode: ConversionMode; } -export const ConversionEdge = 'ConversionEdge'; +export const ConversionEdge = "ConversionEdge"; export function isConversionEdge(edge: unknown): edge is ConversionEdge { return isTypeEdge(edge) && edge.$relation === ConversionEdge; diff --git a/packages/typir/src/services/equality.ts b/packages/typir/src/services/equality.ts index 1525ef14..54c69865 100644 --- a/packages/typir/src/services/equality.ts +++ b/packages/typir/src/services/equality.ts @@ -4,21 +4,26 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from 'langium'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { isSpecificTypirProblem, TypirProblem } from '../utils/utils-definitions.js'; -import { EdgeCachingInformation, TypeRelationshipCaching } from './caching.js'; -import { TypeEdge, isTypeEdge } from '../graph/type-edge.js'; +import { assertUnreachable } from "langium"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { + isSpecificTypirProblem, + TypirProblem, +} from "../utils/utils-definitions.js"; +import { EdgeCachingInformation, TypeRelationshipCaching } from "./caching.js"; +import { TypeEdge, isTypeEdge } from "../graph/type-edge.js"; export interface TypeEqualityProblem extends TypirProblem { - $problem: 'TypeEqualityProblem'; + $problem: "TypeEqualityProblem"; type1: Type; type2: Type; subProblems: TypirProblem[]; // might be empty } -export const TypeEqualityProblem = 'TypeEqualityProblem'; -export function isTypeEqualityProblem(problem: unknown): problem is TypeEqualityProblem { +export const TypeEqualityProblem = "TypeEqualityProblem"; +export function isTypeEqualityProblem( + problem: unknown, +): problem is TypeEqualityProblem { return isSpecificTypirProblem(problem, TypeEqualityProblem); } @@ -30,7 +35,10 @@ export function isTypeEqualityProblem(problem: unknown): problem is TypeEquality */ export interface TypeEquality { areTypesEqual(type1: Type, type2: Type): boolean; - getTypeEqualityProblem(type1: Type, type2: Type): TypeEqualityProblem | undefined; + getTypeEqualityProblem( + type1: Type, + type2: Type, + ): TypeEqualityProblem | undefined; } export class DefaultTypeEquality implements TypeEquality { @@ -44,24 +52,37 @@ export class DefaultTypeEquality implements TypeEquality { return this.getTypeEqualityProblem(type1, type2) === undefined; } - getTypeEqualityProblem(type1: Type, type2: Type): TypeEqualityProblem | undefined { + getTypeEqualityProblem( + type1: Type, + type2: Type, + ): TypeEqualityProblem | undefined { const cache: TypeRelationshipCaching = this.typeRelationships; - const linkData = cache.getRelationshipBidirectional(type1, type2, EqualityEdge); - const equalityCaching = linkData?.cachingInformation ?? 'UNKNOWN'; - - function save(equalityCaching: EdgeCachingInformation, error: TypeEqualityProblem | undefined): void { + const linkData = cache.getRelationshipBidirectional( + type1, + type2, + EqualityEdge, + ); + const equalityCaching = linkData?.cachingInformation ?? "UNKNOWN"; + + function save( + equalityCaching: EdgeCachingInformation, + error: TypeEqualityProblem | undefined, + ): void { const newEdge: EqualityEdge = { $relation: EqualityEdge, from: type1, to: type2, - cachingInformation: 'LINK_EXISTS', + cachingInformation: "LINK_EXISTS", error, }; - cache.setOrUpdateBidirectionalRelationship(newEdge, equalityCaching); + cache.setOrUpdateBidirectionalRelationship( + newEdge, + equalityCaching, + ); } // skip recursive checking - if (equalityCaching === 'PENDING') { + if (equalityCaching === "PENDING") { /** 'undefined' should be correct here ... * - since this relationship will be checked earlier/higher/upper in the call stack again * - since this values is not cached and therefore NOT reused in the earlier call! */ @@ -69,10 +90,10 @@ export class DefaultTypeEquality implements TypeEquality { } // the result is already known - if (equalityCaching === 'LINK_EXISTS') { + if (equalityCaching === "LINK_EXISTS") { return undefined; } - if (equalityCaching === 'NO_LINK') { + if (equalityCaching === "NO_LINK") { return { $problem: TypeEqualityProblem, type1, @@ -82,29 +103,33 @@ export class DefaultTypeEquality implements TypeEquality { } // do the expensive calculation now - if (equalityCaching === 'UNKNOWN') { + if (equalityCaching === "UNKNOWN") { // mark the current relationship as PENDING to detect and resolve cycling checks - save('PENDING', undefined); + save("PENDING", undefined); // do the actual calculation const result = this.calculateEquality(type1, type2); // this allows to cache results (and to re-set the PENDING state) if (result === undefined) { - save('LINK_EXISTS', undefined); + save("LINK_EXISTS", undefined); } else { - save('NO_LINK', result); + save("NO_LINK", result); } return result; } assertUnreachable(equalityCaching); } - protected calculateEquality(type1: Type, type2: Type): TypeEqualityProblem | undefined { + protected calculateEquality( + type1: Type, + type2: Type, + ): TypeEqualityProblem | undefined { if (type1 === type2) { return undefined; } - if (type1.getIdentifier() === type2.getIdentifier()) { // this works, since identifiers are unique! + if (type1.getIdentifier() === type2.getIdentifier()) { + // this works, since identifiers are unique! return undefined; } @@ -124,17 +149,16 @@ export class DefaultTypeEquality implements TypeEquality { $problem: TypeEqualityProblem, type1, type2, - subProblems: [...result1, ...result2] // return the equality problems of both types + subProblems: [...result1, ...result2], // return the equality problems of both types }; } - } export interface EqualityEdge extends TypeEdge { - readonly $relation: 'EqualityEdge'; + readonly $relation: "EqualityEdge"; readonly error: TypeEqualityProblem | undefined; } -export const EqualityEdge = 'EqualityEdge'; +export const EqualityEdge = "EqualityEdge"; export function isEqualityEdge(edge: unknown): edge is EqualityEdge { return isTypeEdge(edge) && edge.$relation === EqualityEdge; diff --git a/packages/typir/src/services/inference.ts b/packages/typir/src/services/inference.ts index 37716b90..838b7ba1 100644 --- a/packages/typir/src/services/inference.ts +++ b/packages/typir/src/services/inference.ts @@ -4,45 +4,54 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from 'langium'; -import { isType, Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { RuleCollectorListener, RuleOptions, RuleRegistry } from '../utils/rule-registration.js'; -import { isSpecificTypirProblem, TypirProblem } from '../utils/utils-definitions.js'; -import { removeFromArray, toArray } from '../utils/utils.js'; -import { LanguageNodeInferenceCaching } from './caching.js'; +import { assertUnreachable } from "langium"; +import { isType, Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { + RuleCollectorListener, + RuleOptions, + RuleRegistry, +} from "../utils/rule-registration.js"; +import { + isSpecificTypirProblem, + TypirProblem, +} from "../utils/utils-definitions.js"; +import { removeFromArray, toArray } from "../utils/utils.js"; +import { LanguageNodeInferenceCaching } from "./caching.js"; export interface InferenceProblem extends TypirProblem { - $problem: 'InferenceProblem'; + $problem: "InferenceProblem"; languageNode: LanguageType; inferenceCandidate?: Type; location: string; rule?: TypeInferenceRule; // for debugging only, since rules have no names (so far); TODO this does not really work with TypeInferenceRuleWithoutInferringChildren subProblems: TypirProblem[]; // might be missing or empty } -export const InferenceProblem = 'InferenceProblem'; -export function isInferenceProblem(problem: unknown): problem is InferenceProblem { +export const InferenceProblem = "InferenceProblem"; +export function isInferenceProblem( + problem: unknown, +): problem is InferenceProblem { return isSpecificTypirProblem(problem, InferenceProblem); } // Type and Value to indicate, that an inference rule is intended for another case, and therefore is unable to infer a type for the current case. -export type InferenceRuleNotApplicable = 'N/A'; // or 'undefined' instead? -export const InferenceRuleNotApplicable = 'N/A'; // or 'undefined' instead? +export type InferenceRuleNotApplicable = "N/A"; // or 'undefined' instead? +export const InferenceRuleNotApplicable = "N/A"; // or 'undefined' instead? export type TypeInferenceResultWithoutInferringChildren = /** the identified type */ - Type | + | Type /** 'N/A' to indicate, that the current inference rule is not applicable for the given language node at all */ - InferenceRuleNotApplicable | + | InferenceRuleNotApplicable /** a language node whose type should be inferred instead */ - LanguageType | + | LanguageType /** an inference problem */ - InferenceProblem; + | InferenceProblem; export type TypeInferenceResultWithInferringChildren = /** the usual results, since it might be possible to determine the type of the parent without its children */ - TypeInferenceResultWithoutInferringChildren | + | TypeInferenceResultWithoutInferringChildren /** the children whos types need to be inferred and taken into account to determine the parent's type */ - LanguageType[]; + | LanguageType[]; /** * Represents a single rule for inference, @@ -53,17 +62,30 @@ export type TypeInferenceResultWithInferringChildren = * Within inference rules, don't take the initialization state of the inferred type into account, * since such inferrence rules might not work for cyclic type definitions. */ -export type TypeInferenceRule = TypeInferenceRuleWithoutInferringChildren | TypeInferenceRuleWithInferringChildren; +export type TypeInferenceRule< + LanguageType, + InputType extends LanguageType = LanguageType, +> = + | TypeInferenceRuleWithoutInferringChildren + | TypeInferenceRuleWithInferringChildren; /** Usual inference rule which don't depend on children's types. */ -export type TypeInferenceRuleWithoutInferringChildren = - (languageNode: InputType, typir: TypirServices) => TypeInferenceResultWithoutInferringChildren; +export type TypeInferenceRuleWithoutInferringChildren< + LanguageType, + InputType extends LanguageType = LanguageType, +> = ( + languageNode: InputType, + typir: TypirServices, +) => TypeInferenceResultWithoutInferringChildren; /** * Inference rule which requires for the type inference of the given parent to take the types of its children into account. * Therefore, the types of the children need to be inferred first. */ -export interface TypeInferenceRuleWithInferringChildren { +export interface TypeInferenceRuleWithInferringChildren< + LanguageType, + InputType extends LanguageType = LanguageType, +> { /** * 1st step is to check, whether this inference rule is applicable to the given language node. * @param languageNode the language node whose type shall be inferred @@ -71,7 +93,10 @@ export interface TypeInferenceRuleWithInferringChildren): TypeInferenceResultWithInferringChildren; + inferTypeWithoutChildren( + languageNode: InputType, + typir: TypirServices, + ): TypeInferenceResultWithInferringChildren; /** * 2nd step is to finally decide about the inferred type. @@ -84,13 +109,22 @@ export interface TypeInferenceRuleWithInferringChildren, typir: TypirServices): Type | InferenceProblem; + inferTypeWithChildrensTypes( + languageNode: InputType, + childrenTypes: Array, + typir: TypirServices, + ): Type | InferenceProblem; } - export interface TypeInferenceCollectorListener { - onAddedInferenceRule(rule: TypeInferenceRule, options: TypeInferenceRuleOptions): void; - onRemovedInferenceRule(rule: TypeInferenceRule, options: TypeInferenceRuleOptions): void; + onAddedInferenceRule( + rule: TypeInferenceRule, + options: TypeInferenceRuleOptions, + ): void; + onRemovedInferenceRule( + rule: TypeInferenceRule, + options: TypeInferenceRuleOptions, + ): void; } export interface TypeInferenceRuleOptions extends RuleOptions { @@ -109,7 +143,9 @@ export interface TypeInferenceCollector { * @param languageNode the language node whose type shall be inferred * @returns the found Type or some inference problems (might be empty), when none of the inference rules were able to infer a type */ - inferType(languageNode: LanguageType): Type | Array>; + inferType( + languageNode: LanguageType, + ): Type | Array>; /** * Registers an inference rule. @@ -118,7 +154,10 @@ export interface TypeInferenceCollector { * @param rule a new inference rule * @param options additional options */ - addInferenceRule(rule: TypeInferenceRule, options?: Partial): void; + addInferenceRule( + rule: TypeInferenceRule, + options?: Partial, + ): void; /** * Deregisters an inference rule. * @param rule the rule to remove @@ -126,19 +165,32 @@ export interface TypeInferenceCollector { * the inference rule might still be registered for the not-specified options. * Listeners will be informed only about those removed options which were existing before. */ - removeInferenceRule(rule: TypeInferenceRule, options?: Partial): void; + removeInferenceRule( + rule: TypeInferenceRule, + options?: Partial, + ): void; addListener(listener: TypeInferenceCollectorListener): void; - removeListener(listener: TypeInferenceCollectorListener): void; + removeListener( + listener: TypeInferenceCollectorListener, + ): void; } - -export class DefaultTypeInferenceCollector implements TypeInferenceCollector, RuleCollectorListener> { - protected readonly ruleRegistry: RuleRegistry, LanguageType>; +export class DefaultTypeInferenceCollector + implements + TypeInferenceCollector, + RuleCollectorListener> +{ + protected readonly ruleRegistry: RuleRegistry< + TypeInferenceRule, + LanguageType + >; protected readonly languageNodeInference: LanguageNodeInferenceCaching; protected readonly services: TypirServices; - protected readonly listeners: Array> = []; + protected readonly listeners: Array< + TypeInferenceCollectorListener + > = []; constructor(services: TypirServices) { this.services = services; @@ -147,7 +199,9 @@ export class DefaultTypeInferenceCollector implements TypeInferenc this.ruleRegistry.addListener(this); } - protected getTypeInferenceRuleOptions(options?: Partial): TypeInferenceRuleOptions { + protected getTypeInferenceRuleOptions( + options?: Partial, + ): TypeInferenceRuleOptions { return { // default values ... languageKey: undefined, @@ -157,7 +211,9 @@ export class DefaultTypeInferenceCollector implements TypeInferenc }; } - protected getLanguageKeys(options?: Partial): Array { + protected getLanguageKeys( + options?: Partial, + ): Array { if (options === undefined || options.languageKey === undefined) { return [undefined]; } else { @@ -165,15 +221,29 @@ export class DefaultTypeInferenceCollector implements TypeInferenc } } - addInferenceRule(rule: TypeInferenceRule, givenOptions?: Partial): void { - this.ruleRegistry.addRule(rule as unknown as TypeInferenceRule, givenOptions); + addInferenceRule( + rule: TypeInferenceRule, + givenOptions?: Partial, + ): void { + this.ruleRegistry.addRule( + rule as unknown as TypeInferenceRule, + givenOptions, + ); } - removeInferenceRule(rule: TypeInferenceRule, optionsToRemove?: Partial): void { - this.ruleRegistry.removeRule(rule as unknown as TypeInferenceRule, optionsToRemove); + removeInferenceRule( + rule: TypeInferenceRule, + optionsToRemove?: Partial, + ): void { + this.ruleRegistry.removeRule( + rule as unknown as TypeInferenceRule, + optionsToRemove, + ); } - inferType(languageNode: LanguageType): Type | Array> { + inferType( + languageNode: LanguageType, + ): Type | Array> { // is the result already in the cache? const cached = this.cacheGet(languageNode); if (cached) { @@ -182,7 +252,9 @@ export class DefaultTypeInferenceCollector implements TypeInferenc // handle recursion loops if (this.pendingGet(languageNode)) { - throw new Error(`There is a recursion loop for inferring the type from ${languageNode}! Probably, there are multiple interfering inference rules.`); + throw new Error( + `There is a recursion loop for inferring the type from ${languageNode}! Probably, there are multiple interfering inference rules.`, + ); } this.pendingSet(languageNode); @@ -201,33 +273,45 @@ export class DefaultTypeInferenceCollector implements TypeInferenc protected checkForError(languageNode: LanguageType): void { if (languageNode === undefined || languageNode === null) { - throw new Error('Language node must be not undefined/null!'); + throw new Error("Language node must be not undefined/null!"); } } - protected inferTypeLogic(languageNode: LanguageType): Type | Array> { + protected inferTypeLogic( + languageNode: LanguageType, + ): Type | Array> { this.checkForError(languageNode); // determine all keys to check - const keysToApply: Array = []; - const languageKey = this.services.Language.getLanguageNodeKey(languageNode); + const keysToApply: Array = []; + const languageKey = + this.services.Language.getLanguageNodeKey(languageNode); if (languageKey === undefined) { keysToApply.push(undefined); } else { keysToApply.push(languageKey); // execute the rules which are associated to the key of the current language node - keysToApply.push(...this.services.Language.getAllSuperKeys(languageKey)); // apply all rules which are associated to super-keys + keysToApply.push( + ...this.services.Language.getAllSuperKeys(languageKey), + ); // apply all rules which are associated to super-keys keysToApply.push(undefined); // rules associated with 'undefined' are applied to all language nodes, apply these rules at the end } // execute all rules wich are associated to the relevant language keys - const collectedInferenceProblems: Array> = []; - const alreadyExecutedRules: Set> = new Set(); + const collectedInferenceProblems: Array< + InferenceProblem + > = []; + const alreadyExecutedRules: Set> = + new Set(); for (const key of keysToApply) { for (const rule of this.ruleRegistry.getRulesByLanguageKey(key)) { if (alreadyExecutedRules.has(rule)) { // don't execute rules multiple times, if they are associated with multiple keys (with overlapping sub-keys) } else { - const result = this.executeSingleInferenceRuleLogic(rule, languageNode, collectedInferenceProblems); + const result = this.executeSingleInferenceRuleLogic( + rule, + languageNode, + collectedInferenceProblems, + ); if (result) { return result; // return the first inferred type, otherwise, check the next inference rules } @@ -242,28 +326,42 @@ export class DefaultTypeInferenceCollector implements TypeInferenc collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: 'found no applicable inference rules', + location: "found no applicable inference rules", subProblems: [], }); } return collectedInferenceProblems; } - protected executeSingleInferenceRuleLogic(rule: TypeInferenceRule, languageNode: LanguageType, collectedInferenceProblems: Array>): Type | undefined { - if (typeof rule === 'function') { + protected executeSingleInferenceRuleLogic( + rule: TypeInferenceRule, + languageNode: LanguageType, + collectedInferenceProblems: Array>, + ): Type | undefined { + if (typeof rule === "function") { // simple case without type inference for children - const ruleResult: TypeInferenceResultWithoutInferringChildren = rule(languageNode, this.services); - return this.inferTypeLogicWithoutChildren(ruleResult, collectedInferenceProblems); - } else if (typeof rule === 'object') { + const ruleResult: TypeInferenceResultWithoutInferringChildren = + rule(languageNode, this.services); + return this.inferTypeLogicWithoutChildren( + ruleResult, + collectedInferenceProblems, + ); + } else if (typeof rule === "object") { // more complex case with inferring the type for children - const ruleResult: TypeInferenceResultWithInferringChildren = rule.inferTypeWithoutChildren(languageNode, this.services); + const ruleResult: TypeInferenceResultWithInferringChildren = + rule.inferTypeWithoutChildren(languageNode, this.services); if (Array.isArray(ruleResult)) { // this rule might match => continue applying this rule // resolve the requested child types const childLanguageNodes = ruleResult; - const actualChildTypes: Array>> = childLanguageNodes.map(child => this.services.Inference.inferType(child)); + const actualChildTypes: Array< + Type | Array> + > = childLanguageNodes.map((child) => + this.services.Inference.inferType(child), + ); // check, whether inferring the children resulted in some other inference problems - const childTypeProblems: Array> = []; + const childTypeProblems: Array> = + []; for (let i = 0; i < actualChildTypes.length; i++) { const child = actualChildTypes[i]; if (Array.isArray(child)) { @@ -280,14 +378,19 @@ export class DefaultTypeInferenceCollector implements TypeInferenc collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: 'inferring depending children', + location: "inferring depending children", rule, subProblems: childTypeProblems, }); return undefined; } else { // the types of all children are successfully inferred - const finalInferenceResult = rule.inferTypeWithChildrensTypes(languageNode, actualChildTypes as Type[], this.services); + const finalInferenceResult = + rule.inferTypeWithChildrensTypes( + languageNode, + actualChildTypes as Type[], + this.services, + ); if (isType(finalInferenceResult)) { // type is inferred! return finalInferenceResult; @@ -298,14 +401,20 @@ export class DefaultTypeInferenceCollector implements TypeInferenc } } } else { - return this.inferTypeLogicWithoutChildren(ruleResult, collectedInferenceProblems); + return this.inferTypeLogicWithoutChildren( + ruleResult, + collectedInferenceProblems, + ); } } else { assertUnreachable(rule); } } - protected inferTypeLogicWithoutChildren(result: TypeInferenceResultWithoutInferringChildren, collectedInferenceProblems: Array>): Type | undefined { + protected inferTypeLogicWithoutChildren( + result: TypeInferenceResultWithoutInferringChildren, + collectedInferenceProblems: Array>, + ): Type | undefined { if (result === InferenceRuleNotApplicable) { // this rule is not applicable at all => ignore this rule return undefined; @@ -328,29 +437,39 @@ export class DefaultTypeInferenceCollector implements TypeInferenc } } - addListener(listener: TypeInferenceCollectorListener): void { this.listeners.push(listener); } - removeListener(listener: TypeInferenceCollectorListener): void { + removeListener( + listener: TypeInferenceCollectorListener, + ): void { removeFromArray(listener, this.listeners); } // This inference collector is notified by the rule registry and forwards these notifications to its own listeners - onAddedRule(rule: TypeInferenceRule, diffOptions: RuleOptions): void { + onAddedRule( + rule: TypeInferenceRule, + diffOptions: RuleOptions, + ): void { // listeners of the composite will be notified about all added inner rules - this.listeners.forEach(listener => listener.onAddedInferenceRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onAddedInferenceRule(rule, diffOptions), + ); } - onRemovedRule(rule: TypeInferenceRule, diffOptions: RuleOptions): void { + onRemovedRule( + rule: TypeInferenceRule, + diffOptions: RuleOptions, + ): void { // clear the cache, since its entries might be created using the removed rule // possible performance improvement: remove only entries which depend on the removed rule? this.cacheClear(); // listeners of the composite will be notified about all removed inner rules - this.listeners.forEach(listener => listener.onRemovedInferenceRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onRemovedInferenceRule(rule, diffOptions), + ); } - /* By default, the central cache of Typir is used. */ protected cacheSet(languageNode: LanguageType, type: Type): void { @@ -376,7 +495,6 @@ export class DefaultTypeInferenceCollector implements TypeInferenc } } - /** * This inference rule uses multiple internal inference rules for doing the type inference. * If one of the child rules returns a type, this type is the result of the composite rule. @@ -385,11 +503,17 @@ export class DefaultTypeInferenceCollector implements TypeInferenc * This composite rule ensures itself, that it is associated to the set of language keys of the inner rules. */ // This design looks a bit ugly ..., but "implements TypeInferenceRuleWithoutInferringChildren" does not work, since it is a function ... -export class CompositeTypeInferenceRule extends DefaultTypeInferenceCollector implements TypeInferenceRuleWithInferringChildren { +export class CompositeTypeInferenceRule + extends DefaultTypeInferenceCollector + implements TypeInferenceRuleWithInferringChildren +{ /** The collector for inference rules, at which this composite rule should be registered. */ protected readonly collectorToRegisterThisRule: TypeInferenceCollector; - constructor(services: TypirServices, collectorToRegisterThisRule: TypeInferenceCollector) { + constructor( + services: TypirServices, + collectorToRegisterThisRule: TypeInferenceCollector, + ) { super(services); this.collectorToRegisterThisRule = collectorToRegisterThisRule; } @@ -405,7 +529,10 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen // nothing to do, since the pending state is not used in this composite rule } - inferTypeWithoutChildren(languageNode: LanguageType, _typir: TypirServices): TypeInferenceResultWithInferringChildren { + inferTypeWithoutChildren( + languageNode: LanguageType, + _typir: TypirServices, + ): TypeInferenceResultWithInferringChildren { // do the type inference const result = this.inferType(languageNode); if (isType(result)) { @@ -419,7 +546,7 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen return >{ $problem: InferenceProblem, languageNode: languageNode, - location: 'sub-rules for inference', + location: "sub-rules for inference", rule: this, subProblems: result, }; @@ -427,11 +554,18 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen } } - inferTypeWithChildrensTypes(_languageNode: LanguageType, _childrenTypes: Array, _typir: TypirServices): Type | InferenceProblem { - throw new Error('This function will not be called.'); + inferTypeWithChildrensTypes( + _languageNode: LanguageType, + _childrenTypes: Array, + _typir: TypirServices, + ): Type | InferenceProblem { + throw new Error("This function will not be called."); } - override onAddedRule(rule: TypeInferenceRule, diffOptions: RuleOptions): void { + override onAddedRule( + rule: TypeInferenceRule, + diffOptions: RuleOptions, + ): void { // an inner rule was added super.onAddedRule(rule, diffOptions); @@ -442,13 +576,18 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen }); } - override onRemovedRule(rule: TypeInferenceRule, diffOptions: RuleOptions): void { + override onRemovedRule( + rule: TypeInferenceRule, + diffOptions: RuleOptions, + ): void { // an inner rule was removed super.onRemovedRule(rule, diffOptions); // remove this composite rule for all language keys for which no inner rules are registered anymore if (diffOptions.languageKey === undefined) { - if (this.ruleRegistry.getRulesByLanguageKey(undefined).length <= 0) { + if ( + this.ruleRegistry.getRulesByLanguageKey(undefined).length <= 0 + ) { this.collectorToRegisterThisRule.removeInferenceRule(this, { ...diffOptions, languageKey: undefined, @@ -456,7 +595,12 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen }); } } else { - const languageKeysToUnregister = toArray(diffOptions.languageKey).filter(key => this.ruleRegistry.getRulesByLanguageKey(key).length <= 0); + const languageKeysToUnregister = toArray( + diffOptions.languageKey, + ).filter( + (key) => + this.ruleRegistry.getRulesByLanguageKey(key).length <= 0, + ); this.collectorToRegisterThisRule.removeInferenceRule(this, { ...diffOptions, languageKey: languageKeysToUnregister, diff --git a/packages/typir/src/services/kind-registry.ts b/packages/typir/src/services/kind-registry.ts index f401d7fc..e4a1a2f0 100644 --- a/packages/typir/src/services/kind-registry.ts +++ b/packages/typir/src/services/kind-registry.ts @@ -4,16 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Kind } from '../kinds/kind.js'; -import { TypirServices } from '../typir.js'; +import { Kind } from "../kinds/kind.js"; +import { TypirServices } from "../typir.js"; export interface KindRegistry { register(kind: Kind): void; - get(type: T['$name']): T | undefined; - getOrCreateKind(type: T['$name'], factory: (services: TypirServices) => T): T; + get(type: T["$name"]): T | undefined; + getOrCreateKind( + type: T["$name"], + factory: (services: TypirServices) => T, + ): T; } -export class DefaultKindRegistry implements KindRegistry { +export class DefaultKindRegistry + implements KindRegistry +{ protected readonly services: TypirServices; protected readonly kinds: Map = new Map(); // name of kind => kind (for an easier look-up) @@ -34,11 +39,14 @@ export class DefaultKindRegistry implements KindRegistry(type: T['$name']): T | undefined { - return this.kinds.get(type) as (T | undefined); + get(type: T["$name"]): T | undefined { + return this.kinds.get(type) as T | undefined; } - getOrCreateKind(type: T['$name'], factory: (services: TypirServices) => T): T { + getOrCreateKind( + type: T["$name"], + factory: (services: TypirServices) => T, + ): T { const existing = this.get(type); if (existing) { return existing; diff --git a/packages/typir/src/services/language.ts b/packages/typir/src/services/language.ts index cc605854..70578ccc 100644 --- a/packages/typir/src/services/language.ts +++ b/packages/typir/src/services/language.ts @@ -40,12 +40,12 @@ export interface LanguageService { getAllSuperKeys(languageKey: string): string[]; } - /** * This default implementation provides no information about the current language. */ -export class DefaultLanguageService implements LanguageService { - +export class DefaultLanguageService + implements LanguageService +{ getLanguageNodeKey(_languageNode: LanguageType): string | undefined { return undefined; } @@ -57,5 +57,4 @@ export class DefaultLanguageService implements LanguageService { +import { Type } from "../graph/type-node.js"; +import { TypeInitializer } from "../initialization/type-initializer.js"; +import { + FunctionFactoryService, + NO_PARAMETER_NAME, +} from "../kinds/function/function-kind.js"; +import { FunctionType } from "../kinds/function/function-type.js"; +import { TypirServices } from "../typir.js"; +import { NameTypePair } from "../utils/utils-definitions.js"; +import { toArray } from "../utils/utils.js"; +import { ValidationProblemAcceptor } from "./validation.js"; + +export interface InferOperatorWithSingleOperand< + LanguageType, + T extends LanguageType = LanguageType, +> { languageKey?: string | string[]; - filter?: (languageNode: LanguageType, operatorName: string) => languageNode is T; + filter?: ( + languageNode: LanguageType, + operatorName: string, + ) => languageNode is T; matching: (languageNode: T, operatorName: string) => boolean; operand: (languageNode: T, operatorName: string) => LanguageType; - validation?: OperatorValidationRule | Array>; + validation?: + | OperatorValidationRule + | Array>; validateArgumentsOfCalls?: boolean | ((languageNode: T) => boolean); } -export interface InferOperatorWithMultipleOperands { +export interface InferOperatorWithMultipleOperands< + LanguageType, + T extends LanguageType = LanguageType, +> { languageKey?: string | string[]; - filter?: (languageNode: LanguageType, operatorName: string) => languageNode is T; + filter?: ( + languageNode: LanguageType, + operatorName: string, + ) => languageNode is T; matching: (languageNode: T, operatorName: string) => boolean; operands: (languageNode: T, operatorName: string) => LanguageType[]; - validation?: OperatorValidationRule | Array>; + validation?: + | OperatorValidationRule + | Array>; validateArgumentsOfCalls?: boolean | ((languageNode: T) => boolean); } -export type OperatorValidationRule = - (operatorCall: T, operatorName: string, operatorType: TypeType, accept: ValidationProblemAcceptor, typir: TypirServices) => void; +export type OperatorValidationRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +> = ( + operatorCall: T, + operatorName: string, + operatorType: TypeType, + accept: ValidationProblemAcceptor, + typir: TypirServices, +) => void; export interface AnyOperatorDetails { name: string; @@ -45,7 +73,9 @@ export interface UnaryOperatorSignature { operand: Type; return: Type; } -interface CreateUnaryOperatorDetails extends UnaryOperatorDetails { // only internally used for collecting all information with the chaining API +interface CreateUnaryOperatorDetails + extends UnaryOperatorDetails { + // only internally used for collecting all information with the chaining API inferenceRules: Array>; } @@ -58,7 +88,9 @@ export interface BinaryOperatorSignature { right: Type; return: Type; } -interface CreateBinaryOperatorDetails extends BinaryOperatorDetails { // only internally used for collecting all information with the chaining API +interface CreateBinaryOperatorDetails + extends BinaryOperatorDetails { + // only internally used for collecting all information with the chaining API inferenceRules: Array>; } @@ -72,7 +104,9 @@ export interface TernaryOperatorSignature { third: Type; return: Type; } -interface CreateTernaryOperatorDetails extends TernaryOperatorDetails { // only internally used for collecting all information with the chaining API +interface CreateTernaryOperatorDetails + extends TernaryOperatorDetails { + // only internally used for collecting all information with the chaining API inferenceRules: Array>; } @@ -80,37 +114,59 @@ export interface GenericOperatorDetails extends AnyOperatorDetails { outputType: Type; inputParameter: NameTypePair[]; } -interface CreateGenericOperatorDetails extends GenericOperatorDetails { // only internally used for collecting all information with the chaining API - inferenceRules: Array | InferOperatorWithMultipleOperands>; +interface CreateGenericOperatorDetails + extends GenericOperatorDetails { + // only internally used for collecting all information with the chaining API + inferenceRules: Array< + | InferOperatorWithSingleOperand + | InferOperatorWithMultipleOperands + >; } export interface OperatorFactoryService { - createUnary(typeDetails: UnaryOperatorDetails): OperatorConfigurationUnaryChain; - createBinary(typeDetails: BinaryOperatorDetails): OperatorConfigurationBinaryChain; - createTernary(typeDetails: TernaryOperatorDetails): OperatorConfigurationTernaryChain; + createUnary( + typeDetails: UnaryOperatorDetails, + ): OperatorConfigurationUnaryChain; + createBinary( + typeDetails: BinaryOperatorDetails, + ): OperatorConfigurationBinaryChain; + createTernary( + typeDetails: TernaryOperatorDetails, + ): OperatorConfigurationTernaryChain; /** This function allows to create a single operator with arbitrary input operands. */ - createGeneric(typeDetails: GenericOperatorDetails): OperatorConfigurationGenericChain; + createGeneric( + typeDetails: GenericOperatorDetails, + ): OperatorConfigurationGenericChain; } export interface OperatorConfigurationUnaryChain { - inferenceRule(rule: InferOperatorWithSingleOperand): OperatorConfigurationUnaryChain; + inferenceRule( + rule: InferOperatorWithSingleOperand, + ): OperatorConfigurationUnaryChain; finish(): Array>; } export interface OperatorConfigurationBinaryChain { - inferenceRule(rule: InferOperatorWithMultipleOperands): OperatorConfigurationBinaryChain; + inferenceRule( + rule: InferOperatorWithMultipleOperands, + ): OperatorConfigurationBinaryChain; finish(): Array>; } export interface OperatorConfigurationTernaryChain { - inferenceRule(rule: InferOperatorWithMultipleOperands): OperatorConfigurationTernaryChain; + inferenceRule( + rule: InferOperatorWithMultipleOperands, + ): OperatorConfigurationTernaryChain; finish(): Array>; } export interface OperatorConfigurationGenericChain { - inferenceRule(rule: InferOperatorWithSingleOperand | InferOperatorWithMultipleOperands): OperatorConfigurationGenericChain; + inferenceRule( + rule: + | InferOperatorWithSingleOperand + | InferOperatorWithMultipleOperands, + ): OperatorConfigurationGenericChain; finish(): TypeInitializer; } - /** * This implementation realizes operators as functions and creates types of kind 'function'. * If Typir does not use the function kind so far, it will be automatically added. @@ -125,36 +181,62 @@ export interface OperatorConfigurationGenericChain { * * All operands are mandatory. */ -export class DefaultOperatorFactory implements OperatorFactoryService { +export class DefaultOperatorFactory + implements OperatorFactoryService +{ protected readonly services: TypirServices; constructor(services: TypirServices) { this.services = services; } - createUnary(typeDetails: UnaryOperatorDetails): OperatorConfigurationUnaryChain { - return new OperatorConfigurationUnaryChainImpl(this.services, typeDetails); + createUnary( + typeDetails: UnaryOperatorDetails, + ): OperatorConfigurationUnaryChain { + return new OperatorConfigurationUnaryChainImpl( + this.services, + typeDetails, + ); } - createBinary(typeDetails: BinaryOperatorDetails): OperatorConfigurationBinaryChain { - return new OperatorConfigurationBinaryChainImpl(this.services, typeDetails); + createBinary( + typeDetails: BinaryOperatorDetails, + ): OperatorConfigurationBinaryChain { + return new OperatorConfigurationBinaryChainImpl( + this.services, + typeDetails, + ); } - createTernary(typeDetails: TernaryOperatorDetails): OperatorConfigurationTernaryChain { - return new OperatorConfigurationTernaryChainImpl(this.services, typeDetails); + createTernary( + typeDetails: TernaryOperatorDetails, + ): OperatorConfigurationTernaryChain { + return new OperatorConfigurationTernaryChainImpl( + this.services, + typeDetails, + ); } - createGeneric(typeDetails: GenericOperatorDetails): OperatorConfigurationGenericChain { - return new OperatorConfigurationGenericChainImpl(this.services, typeDetails); + createGeneric( + typeDetails: GenericOperatorDetails, + ): OperatorConfigurationGenericChain { + return new OperatorConfigurationGenericChainImpl( + this.services, + typeDetails, + ); } } - -class OperatorConfigurationUnaryChainImpl implements OperatorConfigurationUnaryChain { +class OperatorConfigurationUnaryChainImpl + implements OperatorConfigurationUnaryChain +{ protected readonly services: TypirServices; protected readonly typeDetails: CreateUnaryOperatorDetails; - constructor(services: TypirServices, typeDetails: UnaryOperatorDetails) { + constructor( + services: TypirServices, + typeDetails: UnaryOperatorDetails, + ) { this.services = services; this.typeDetails = { ...typeDetails, @@ -162,8 +244,12 @@ class OperatorConfigurationUnaryChainImpl implements OperatorConfi }; } - inferenceRule(rule: InferOperatorWithSingleOperand): OperatorConfigurationUnaryChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferOperatorWithSingleOperand); + inferenceRule( + rule: InferOperatorWithSingleOperand, + ): OperatorConfigurationUnaryChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferOperatorWithSingleOperand, + ); return this; } @@ -171,26 +257,36 @@ class OperatorConfigurationUnaryChainImpl implements OperatorConfi const signatures = toSignatureArray(this.typeDetails); const result: Array> = []; for (const signature of signatures) { - const generic = new OperatorConfigurationGenericChainImpl(this.services, { - name: this.typeDetails.name, - outputType: signature.return, - inputParameter: [ - { name: 'operand', type: signature.operand }, - ], - }); + const generic = new OperatorConfigurationGenericChainImpl( + this.services, + { + name: this.typeDetails.name, + outputType: signature.return, + inputParameter: [ + { name: "operand", type: signature.operand }, + ], + }, + ); // the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created! - this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule)); + this.typeDetails.inferenceRules.forEach((rule) => + generic.inferenceRule(rule), + ); result.push(generic.finish()); } return result; } } -class OperatorConfigurationBinaryChainImpl implements OperatorConfigurationBinaryChain { +class OperatorConfigurationBinaryChainImpl + implements OperatorConfigurationBinaryChain +{ protected readonly services: TypirServices; protected readonly typeDetails: CreateBinaryOperatorDetails; - constructor(services: TypirServices, typeDetails: BinaryOperatorDetails) { + constructor( + services: TypirServices, + typeDetails: BinaryOperatorDetails, + ) { this.services = services; this.typeDetails = { ...typeDetails, @@ -198,8 +294,12 @@ class OperatorConfigurationBinaryChainImpl implements OperatorConf }; } - inferenceRule(rule: InferOperatorWithMultipleOperands): OperatorConfigurationBinaryChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferOperatorWithMultipleOperands); + inferenceRule( + rule: InferOperatorWithMultipleOperands, + ): OperatorConfigurationBinaryChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferOperatorWithMultipleOperands, + ); return this; } @@ -207,27 +307,37 @@ class OperatorConfigurationBinaryChainImpl implements OperatorConf const signatures = toSignatureArray(this.typeDetails); const result: Array> = []; for (const signature of signatures) { - const generic = new OperatorConfigurationGenericChainImpl(this.services, { - name: this.typeDetails.name, - outputType: signature.return, - inputParameter: [ - { name: 'left', type: signature.left}, - { name: 'right', type: signature.right}, - ], - }); + const generic = new OperatorConfigurationGenericChainImpl( + this.services, + { + name: this.typeDetails.name, + outputType: signature.return, + inputParameter: [ + { name: "left", type: signature.left }, + { name: "right", type: signature.right }, + ], + }, + ); // the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created! - this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule)); + this.typeDetails.inferenceRules.forEach((rule) => + generic.inferenceRule(rule), + ); result.push(generic.finish()); } return result; } } -class OperatorConfigurationTernaryChainImpl implements OperatorConfigurationTernaryChain { +class OperatorConfigurationTernaryChainImpl + implements OperatorConfigurationTernaryChain +{ protected readonly services: TypirServices; protected readonly typeDetails: CreateTernaryOperatorDetails; - constructor(services: TypirServices, typeDetails: TernaryOperatorDetails) { + constructor( + services: TypirServices, + typeDetails: TernaryOperatorDetails, + ) { this.services = services; this.typeDetails = { ...typeDetails, @@ -235,8 +345,12 @@ class OperatorConfigurationTernaryChainImpl implements OperatorCon }; } - inferenceRule(rule: InferOperatorWithMultipleOperands): OperatorConfigurationTernaryChain { - this.typeDetails.inferenceRules.push(rule as unknown as InferOperatorWithMultipleOperands); + inferenceRule( + rule: InferOperatorWithMultipleOperands, + ): OperatorConfigurationTernaryChain { + this.typeDetails.inferenceRules.push( + rule as unknown as InferOperatorWithMultipleOperands, + ); return this; } @@ -244,28 +358,38 @@ class OperatorConfigurationTernaryChainImpl implements OperatorCon const signatures = toSignatureArray(this.typeDetails); const result: Array> = []; for (const signature of signatures) { - const generic = new OperatorConfigurationGenericChainImpl(this.services, { - name: this.typeDetails.name, - outputType: signature.return, - inputParameter: [ - { name: 'first', type: signature.first }, - { name: 'second', type: signature.second }, - { name: 'third', type: signature.third }, - ], - }); + const generic = new OperatorConfigurationGenericChainImpl( + this.services, + { + name: this.typeDetails.name, + outputType: signature.return, + inputParameter: [ + { name: "first", type: signature.first }, + { name: "second", type: signature.second }, + { name: "third", type: signature.third }, + ], + }, + ); // the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created! - this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule)); + this.typeDetails.inferenceRules.forEach((rule) => + generic.inferenceRule(rule), + ); result.push(generic.finish()); } return result; } } -class OperatorConfigurationGenericChainImpl implements OperatorConfigurationGenericChain { +class OperatorConfigurationGenericChainImpl + implements OperatorConfigurationGenericChain +{ protected readonly services: TypirServices; protected readonly typeDetails: CreateGenericOperatorDetails; - constructor(services: TypirServices, typeDetails: GenericOperatorDetails) { + constructor( + services: TypirServices, + typeDetails: GenericOperatorDetails, + ) { this.services = services; this.typeDetails = { ...typeDetails, @@ -273,8 +397,16 @@ class OperatorConfigurationGenericChainImpl implements OperatorCon }; } - inferenceRule(rule: InferOperatorWithSingleOperand | InferOperatorWithMultipleOperands): OperatorConfigurationGenericChain { - this.typeDetails.inferenceRules.push(rule as unknown as (InferOperatorWithSingleOperand | InferOperatorWithMultipleOperands)); + inferenceRule( + rule: + | InferOperatorWithSingleOperand + | InferOperatorWithMultipleOperands, + ): OperatorConfigurationGenericChain { + this.typeDetails.inferenceRules.push( + rule as unknown as + | InferOperatorWithSingleOperand + | InferOperatorWithMultipleOperands, + ); return this; } @@ -286,30 +418,72 @@ class OperatorConfigurationGenericChainImpl implements OperatorCon // create the operator as type of kind 'function' const newOperatorType = functionFactory.create({ functionName: operatorName, - outputParameter: { name: NO_PARAMETER_NAME, type: this.typeDetails.outputType }, + outputParameter: { + name: NO_PARAMETER_NAME, + type: this.typeDetails.outputType, + }, inputParameters: this.typeDetails.inputParameter, }); // infer the operator when the operator is called! for (const inferenceRule of this.typeDetails.inferenceRules) { newOperatorType.inferenceRuleForCalls({ languageKey: inferenceRule.languageKey, - filter: inferenceRule.filter ? ((languageNode: LanguageType): languageNode is LanguageType => inferenceRule.filter!(languageNode, this.typeDetails.name)) : undefined, - matching: (languageNode: LanguageType) => inferenceRule.matching(languageNode, this.typeDetails.name), - inputArguments: (languageNode: LanguageType) => this.getInputArguments(inferenceRule, languageNode), - validation: toArray(inferenceRule.validation).map(validationRule => - (functionCall: LanguageType, functionType: FunctionType, accept: ValidationProblemAcceptor, typir: TypirServices) => validationRule(functionCall, operatorName, functionType, accept, typir)), - validateArgumentsOfFunctionCalls: inferenceRule.validateArgumentsOfCalls, + filter: inferenceRule.filter + ? ( + languageNode: LanguageType, + ): languageNode is LanguageType => + inferenceRule.filter!( + languageNode, + this.typeDetails.name, + ) + : undefined, + matching: (languageNode: LanguageType) => + inferenceRule.matching(languageNode, this.typeDetails.name), + inputArguments: (languageNode: LanguageType) => + this.getInputArguments(inferenceRule, languageNode), + validation: toArray(inferenceRule.validation).map( + (validationRule) => + ( + functionCall: LanguageType, + functionType: FunctionType, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ) => + validationRule( + functionCall, + operatorName, + functionType, + accept, + typir, + ), + ), + validateArgumentsOfFunctionCalls: + inferenceRule.validateArgumentsOfCalls, }); } // operators have no declaration in the code => no inference rule for the operator declaration! - return newOperatorType.finish() as unknown as TypeInitializer; + return newOperatorType.finish() as unknown as TypeInitializer< + Type, + LanguageType + >; } - protected getInputArguments(inferenceRule: InferOperatorWithSingleOperand | InferOperatorWithMultipleOperands, languageNode: LanguageType): LanguageType[] { - return 'operands' in inferenceRule - ? (inferenceRule as InferOperatorWithMultipleOperands).operands(languageNode, this.typeDetails.name) - : [(inferenceRule as InferOperatorWithSingleOperand).operand(languageNode, this.typeDetails.name)]; + protected getInputArguments( + inferenceRule: + | InferOperatorWithSingleOperand + | InferOperatorWithMultipleOperands, + languageNode: LanguageType, + ): LanguageType[] { + return "operands" in inferenceRule + ? ( + inferenceRule as InferOperatorWithMultipleOperands + ).operands(languageNode, this.typeDetails.name) + : [ + ( + inferenceRule as InferOperatorWithSingleOperand + ).operand(languageNode, this.typeDetails.name), + ]; } protected getFunctionFactory(): FunctionFactoryService { @@ -317,17 +491,13 @@ class OperatorConfigurationGenericChainImpl implements OperatorCon } } - -function toSignatureArray(values: { - signature?: T; - signatures?: T[]; -}): T[] { +function toSignatureArray(values: { signature?: T; signatures?: T[] }): T[] { const result = [...toArray(values.signatures)]; // create a new array in order to prevent side-effects in the given array if (values.signature) { result.push(values.signature); } if (result.length <= 0) { - throw new Error('At least one signature must be given!'); + throw new Error("At least one signature must be given!"); } return result; } diff --git a/packages/typir/src/services/printing.ts b/packages/typir/src/services/printing.ts index 6ad9390c..cd2ff29e 100644 --- a/packages/typir/src/services/printing.ts +++ b/packages/typir/src/services/printing.ts @@ -4,15 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from '../graph/type-node.js'; -import { TypirProblem } from '../utils/utils-definitions.js'; -import { IndexedTypeConflict, ValueConflict, isIndexedTypeConflict, isValueConflict } from '../utils/utils-type-comparison.js'; -import { toArray } from '../utils/utils.js'; -import { AssignabilityProblem, isAssignabilityProblem } from './assignability.js'; -import { TypeEqualityProblem, isTypeEqualityProblem } from './equality.js'; -import { InferenceProblem, isInferenceProblem } from './inference.js'; -import { SubTypeProblem, isSubTypeProblem } from './subtype.js'; -import { ValidationProblem, isValidationProblem } from './validation.js'; +import { Type } from "../graph/type-node.js"; +import { TypirProblem } from "../utils/utils-definitions.js"; +import { + IndexedTypeConflict, + ValueConflict, + isIndexedTypeConflict, + isValueConflict, +} from "../utils/utils-type-comparison.js"; +import { toArray } from "../utils/utils.js"; +import { + AssignabilityProblem, + isAssignabilityProblem, +} from "./assignability.js"; +import { TypeEqualityProblem, isTypeEqualityProblem } from "./equality.js"; +import { InferenceProblem, isInferenceProblem } from "./inference.js"; +import { SubTypeProblem, isSubTypeProblem } from "./subtype.js"; +import { ValidationProblem, isValidationProblem } from "./validation.js"; export interface ProblemPrinter { printValueConflict(problem: ValueConflict): string; @@ -21,12 +29,15 @@ export interface ProblemPrinter { printSubTypeProblem(problem: SubTypeProblem): string; printTypeEqualityProblem(problem: TypeEqualityProblem): string; printInferenceProblem(problem: InferenceProblem): string; - printValidationProblem(problem: ValidationProblem): string + printValidationProblem(problem: ValidationProblem): string; printTypirProblem(problem: TypirProblem): string; printTypirProblems(problems: TypirProblem[]): string; - printLanguageNode(languageNode: LanguageType, sentenceBegin: boolean): string; + printLanguageNode( + languageNode: LanguageType, + sentenceBegin: boolean, + ): string; /** * This function should be used by other services, instead of using type.getName(). @@ -45,10 +56,10 @@ export interface ProblemPrinter { printTypeUserRepresentation(type: Type): string; } -export class DefaultTypeConflictPrinter implements ProblemPrinter { - - constructor() { - } +export class DefaultTypeConflictPrinter + implements ProblemPrinter +{ + constructor() {} printValueConflict(problem: ValueConflict, level: number = 0): string { let result = `At ${problem.location}, `; @@ -67,10 +78,13 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< return result; } - printIndexedTypeConflict(problem: IndexedTypeConflict, level: number = 0): string { + printIndexedTypeConflict( + problem: IndexedTypeConflict, + level: number = 0, + ): string { const left = problem.expected; const right = problem.actual; - let result = ''; + let result = ""; if (problem.propertyName) { if (problem.propertyIndex) { result += `For property '${problem.propertyName} at index ${problem.propertyIndex}', `; @@ -80,7 +94,7 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< } else if (problem.propertyIndex) { result += `At index ${problem.propertyIndex}, `; } else { - result += 'At an unknown location, '; + result += "At an unknown location, "; } if (left !== undefined && right !== undefined) { result += `the types '${this.printTypeName(left)}' and '${this.printTypeName(right)}' do not match.`; @@ -89,14 +103,17 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< } else if (left === undefined && right !== undefined) { result += `there is no type on the left to match with the type '${this.printTypeName(right)}' on the right.`; } else { - result += 'both types are unclear.'; + result += "both types are unclear."; } result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); return result; } - printAssignabilityProblem(problem: AssignabilityProblem, level: number = 0): string { + printAssignabilityProblem( + problem: AssignabilityProblem, + level: number = 0, + ): string { let result = `The type '${this.printTypeName(problem.source)}' is not assignable to the type '${this.printTypeName(problem.target)}'.`; result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); @@ -110,27 +127,37 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< return result; } - printTypeEqualityProblem(problem: TypeEqualityProblem, level: number = 0): string { + printTypeEqualityProblem( + problem: TypeEqualityProblem, + level: number = 0, + ): string { let result = `The types '${this.printTypeName(problem.type1)}' and '${this.printTypeName(problem.type2)}' are not equal.`; result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); return result; } - printInferenceProblem(problem: InferenceProblem, level: number = 0): string { + printInferenceProblem( + problem: InferenceProblem, + level: number = 0, + ): string { let result = `While inferring the type for ${this.printLanguageNode(problem.languageNode)}, at ${problem.location}`; if (problem.inferenceCandidate) { result += ` of the type '${this.printTypeName(problem.inferenceCandidate)}' as candidate to infer`; } - result += ', some problems occurred.'; + result += ", some problems occurred."; // Since Rules have no name, it is not possible to print problem.rule here. result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); return result; } - printValidationProblem(problem: ValidationProblem, level: number = 0): string { - let result = `While validating ${this.printLanguageNode(problem.languageNode)}, this ${problem.severity} is found: ${problem.message}`.trim(); + printValidationProblem( + problem: ValidationProblem, + level: number = 0, + ): string { + let result = + `While validating ${this.printLanguageNode(problem.languageNode)}, this ${problem.severity} is found: ${problem.message}`.trim(); result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); return result; @@ -157,11 +184,14 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< } printTypirProblems(problems: TypirProblem[], level: number = 0): string { - return problems.map(p => this.printTypirProblem(p, level)).join('\n'); + return problems.map((p) => this.printTypirProblem(p, level)).join("\n"); } - printLanguageNode(languageNode: LanguageType, sentenceBegin: boolean = false): string { - return `${sentenceBegin ? 'T' : 't'}he language node '${languageNode}'`; + printLanguageNode( + languageNode: LanguageType, + sentenceBegin: boolean = false, + ): string { + return `${sentenceBegin ? "T" : "t"}he language node '${languageNode}'`; } printTypeName(type: Type): string { @@ -172,10 +202,14 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< return type.getUserRepresentation(); } - protected printSubProblems(result: string, subProblems: undefined | TypirProblem[], level: number = 0): string { + protected printSubProblems( + result: string, + subProblems: undefined | TypirProblem[], + level: number = 0, + ): string { const problems = toArray(subProblems); if (problems.length >= 1) { - return result + '\n' + this.printTypirProblems(problems, level + 1); + return result + "\n" + this.printTypirProblems(problems, level + 1); } else { return result; } diff --git a/packages/typir/src/services/subtype.ts b/packages/typir/src/services/subtype.ts index e3592f26..ca7e5214 100644 --- a/packages/typir/src/services/subtype.ts +++ b/packages/typir/src/services/subtype.ts @@ -4,28 +4,28 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from '../graph/graph-algorithms.js'; -import { isTypeEdge, TypeEdge } from '../graph/type-edge.js'; -import { TypeGraph } from '../graph/type-graph.js'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { TypirProblem } from '../utils/utils-definitions.js'; +import { GraphAlgorithms } from "../graph/graph-algorithms.js"; +import { isTypeEdge, TypeEdge } from "../graph/type-edge.js"; +import { TypeGraph } from "../graph/type-graph.js"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { TypirProblem } from "../utils/utils-definitions.js"; export interface SubTypeProblem extends TypirProblem { - $problem: 'SubTypeProblem'; - $result: 'SubTypeResult'; + $problem: "SubTypeProblem"; + $result: "SubTypeResult"; superType: Type; subType: Type; result: false; subProblems: TypirProblem[]; // might be empty } -export const SubTypeProblem = 'SubTypeProblem'; +export const SubTypeProblem = "SubTypeProblem"; export function isSubTypeProblem(problem: unknown): problem is SubTypeProblem { return isSubTypeResult(problem) && problem.result === false; } export interface SubTypeSuccess { - $result: 'SubTypeResult'; + $result: "SubTypeResult"; superType: Type; subType: Type; result: true; @@ -36,12 +36,15 @@ export function isSubTypeSuccess(success: unknown): success is SubTypeSuccess { } export type SubTypeResult = SubTypeSuccess | SubTypeProblem; -export const SubTypeResult = 'SubTypeResult'; +export const SubTypeResult = "SubTypeResult"; export function isSubTypeResult(result: unknown): result is SubTypeResult { - return typeof result === 'object' && result !== null && ((result as SubTypeResult).$result === SubTypeResult); + return ( + typeof result === "object" && + result !== null && + (result as SubTypeResult).$result === SubTypeResult + ); } - export interface MarkSubTypeOptions { /** If selected, it will be checked, whether cycles in sub-type relationships exists now at the involved types. * Types which internally manage their sub-type relationships themselves usually don't check for cycles, @@ -58,13 +61,19 @@ export interface MarkSubTypeOptions { */ export interface SubType { isSubType(subType: Type, superType: Type): boolean; - getSubTypeProblem(subType: Type, superType: Type): SubTypeProblem | undefined; + getSubTypeProblem( + subType: Type, + superType: Type, + ): SubTypeProblem | undefined; getSubTypeResult(subType: Type, superType: Type): SubTypeResult; - markAsSubType(subType: Type, superType: Type, options?: Partial): void; + markAsSubType( + subType: Type, + superType: Type, + options?: Partial, + ): void; } - /** * The default implementation for the SubType service. * It assumes that all known types and all their sub-type relationships are explicitly encoded in the type graph. @@ -85,14 +94,19 @@ export class DefaultSubType implements SubType { return isSubTypeSuccess(this.getSubTypeResult(subType, superType)); } - getSubTypeProblem(subType: Type, superType: Type): SubTypeProblem | undefined { + getSubTypeProblem( + subType: Type, + superType: Type, + ): SubTypeProblem | undefined { const result = this.getSubTypeResult(subType, superType); return isSubTypeProblem(result) ? result : undefined; } getSubTypeResult(subType: Type, superType: Type): SubTypeResult { // search for a transitive sub-type relationship - const path = this.algorithms.getEdgePath(subType, superType, [SubTypeEdge]); + const path = this.algorithms.getEdgePath(subType, superType, [ + SubTypeEdge, + ]); if (path.length >= 1) { return { $result: SubTypeResult, @@ -114,19 +128,27 @@ export class DefaultSubType implements SubType { } protected getSubTypeEdge(from: Type, to: Type): SubTypeEdge | undefined { - return from.getOutgoingEdges(SubTypeEdge).find(edge => edge.to === to); + return from + .getOutgoingEdges(SubTypeEdge) + .find((edge) => edge.to === to); } - protected collectMarkSubTypeOptions(options?: Partial): MarkSubTypeOptions { + protected collectMarkSubTypeOptions( + options?: Partial, + ): MarkSubTypeOptions { return { // the default values: checkForCycles: true, // the actually overriden values: - ...options + ...options, }; } - markAsSubType(subType: Type, superType: Type, options: MarkSubTypeOptions): void { + markAsSubType( + subType: Type, + superType: Type, + options: MarkSubTypeOptions, + ): void { const actualOptions = this.collectMarkSubTypeOptions(options); let edge = this.getSubTypeEdge(subType, superType); if (!edge) { @@ -134,30 +156,35 @@ export class DefaultSubType implements SubType { $relation: SubTypeEdge, from: subType, to: superType, - cachingInformation: 'LINK_EXISTS', + cachingInformation: "LINK_EXISTS", error: undefined, }; this.graph.addEdge(edge); } else { - edge.cachingInformation = 'LINK_EXISTS'; + edge.cachingInformation = "LINK_EXISTS"; } // check for cycles if (actualOptions.checkForCycles) { - const hasIntroducedCycle = this.algorithms.existsEdgePath(subType, subType, [SubTypeEdge]); + const hasIntroducedCycle = this.algorithms.existsEdgePath( + subType, + subType, + [SubTypeEdge], + ); if (hasIntroducedCycle) { - throw new Error(`Adding the sub-type relationship from ${subType.getIdentifier()} to ${superType.getIdentifier()} has introduced a cycle in the type graph.`); + throw new Error( + `Adding the sub-type relationship from ${subType.getIdentifier()} to ${superType.getIdentifier()} has introduced a cycle in the type graph.`, + ); } } } - } export interface SubTypeEdge extends TypeEdge { - readonly $relation: 'SubTypeEdge'; + readonly $relation: "SubTypeEdge"; readonly error: SubTypeProblem | undefined; } -export const SubTypeEdge = 'SubTypeEdge'; +export const SubTypeEdge = "SubTypeEdge"; export function isSubTypeEdge(edge: unknown): edge is SubTypeEdge { return isTypeEdge(edge) && edge.$relation === SubTypeEdge; diff --git a/packages/typir/src/services/validation.ts b/packages/typir/src/services/validation.ts index d83d75c9..c3dffeab 100644 --- a/packages/typir/src/services/validation.ts +++ b/packages/typir/src/services/validation.ts @@ -4,18 +4,31 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, isType } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { RuleCollectorListener, RuleOptions, RuleRegistry } from '../utils/rule-registration.js'; -import { TypirProblem, isSpecificTypirProblem } from '../utils/utils-definitions.js'; -import { TypeCheckStrategy, createTypeCheckStrategy } from '../utils/utils-type-comparison.js'; -import { removeFromArray, toArray } from '../utils/utils.js'; -import { TypeInferenceCollector } from './inference.js'; -import { ProblemPrinter } from './printing.js'; - -export type Severity = 'error' | 'warning' | 'info' | 'hint'; - -export interface ValidationMessageDetails { +import { Type, isType } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { + RuleCollectorListener, + RuleOptions, + RuleRegistry, +} from "../utils/rule-registration.js"; +import { + TypirProblem, + isSpecificTypirProblem, +} from "../utils/utils-definitions.js"; +import { + TypeCheckStrategy, + createTypeCheckStrategy, +} from "../utils/utils-type-comparison.js"; +import { removeFromArray, toArray } from "../utils/utils.js"; +import { TypeInferenceCollector } from "./inference.js"; +import { ProblemPrinter } from "./printing.js"; + +export type Severity = "error" | "warning" | "info" | "hint"; + +export interface ValidationMessageDetails< + LanguageType, + T extends LanguageType = LanguageType, +> { languageNode: T; languageProperty?: string; // name of a property of the language node; TODO make this type-safe! languageIndex?: number; // index, if 'languageProperty' is an Array property @@ -23,21 +36,38 @@ export interface ValidationMessageDetails extends ValidationMessageDetails, TypirProblem { - $problem: 'ValidationProblem'; +export interface ValidationProblem< + LanguageType, + T extends LanguageType = LanguageType, +> extends ValidationMessageDetails, + TypirProblem { + $problem: "ValidationProblem"; subProblems?: TypirProblem[]; } -export const ValidationProblem = 'ValidationProblem'; -export function isValidationProblem(problem: unknown): problem is ValidationProblem { +export const ValidationProblem = "ValidationProblem"; +export function isValidationProblem< + LanguageType, + T extends LanguageType = LanguageType, +>(problem: unknown): problem is ValidationProblem { return isSpecificTypirProblem(problem, ValidationProblem); } /** Don't specify the $problem-property. */ -export type ReducedValidationProblem = Omit, '$problem'>; - -export type ValidationProblemAcceptor = (problem: ReducedValidationProblem) => void; - -export type ValidationRule = +export type ReducedValidationProblem< + LanguageType, + T extends LanguageType = LanguageType, +> = Omit, "$problem">; + +export type ValidationProblemAcceptor = < + T extends LanguageType = LanguageType, +>( + problem: ReducedValidationProblem, +) => void; + +export type ValidationRule< + LanguageType, + InputType extends LanguageType = LanguageType, +> = | ValidationRuleFunctional | ValidationRuleLifecycle; @@ -45,8 +75,14 @@ export type ValidationRule = - (languageNode: InputType, accept: ValidationProblemAcceptor, typir: TypirServices) => void; +export type ValidationRuleFunctional< + LanguageType, + InputType extends LanguageType = LanguageType, +> = ( + languageNode: InputType, + accept: ValidationProblemAcceptor, + typir: TypirServices, +) => void; /** * Describes a complex validation rule which has a state. @@ -54,43 +90,87 @@ export type ValidationRuleFunctional { - beforeValidation?: (languageRoot: RootType, accept: ValidationProblemAcceptor, typir: TypirServices) => void; +export interface ValidationRuleLifecycle< + LanguageType, + RootType extends LanguageType = LanguageType, + InputType extends LanguageType = LanguageType, +> { + beforeValidation?: ( + languageRoot: RootType, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ) => void; validation: ValidationRuleFunctional; - afterValidation?: (languageRoot: RootType, accept: ValidationProblemAcceptor, typir: TypirServices) => void; + afterValidation?: ( + languageRoot: RootType, + accept: ValidationProblemAcceptor, + typir: TypirServices, + ) => void; } - /** Annotate types after the validation with additional information in order to ease the creation of usefull messages. */ export interface AnnotatedTypeAfterValidation { type: Type; userRepresentation: string; name: string; } -export type ValidationMessageProvider = - (actual: AnnotatedTypeAfterValidation, expected: AnnotatedTypeAfterValidation) => Partial>; +export type ValidationMessageProvider< + LanguageType, + T extends LanguageType = LanguageType, +> = ( + actual: AnnotatedTypeAfterValidation, + expected: AnnotatedTypeAfterValidation, +) => Partial>; export interface ValidationConstraints { - ensureNodeIsAssignable( - sourceNode: S | undefined, expected: Type | undefined | E, + ensureNodeIsAssignable< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + expected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider): void; - ensureNodeIsEquals( - sourceNode: S | undefined, expected: Type | undefined | E, + message: ValidationMessageProvider, + ): void; + ensureNodeIsEquals< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + expected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider): void; - ensureNodeHasNotType( - sourceNode: S | undefined, notExpected: Type | undefined | E, + message: ValidationMessageProvider, + ): void; + ensureNodeHasNotType< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + notExpected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider): void; - - ensureNodeRelatedWithType( - languageNode: S | undefined, expected: Type | undefined | E, strategy: TypeCheckStrategy, negated: boolean, + message: ValidationMessageProvider, + ): void; + + ensureNodeRelatedWithType< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + languageNode: S | undefined, + expected: Type | undefined | E, + strategy: TypeCheckStrategy, + negated: boolean, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider): void; + message: ValidationMessageProvider, + ): void; } -export class DefaultValidationConstraints implements ValidationConstraints { +export class DefaultValidationConstraints + implements ValidationConstraints +{ protected readonly services: TypirServices; protected readonly inference: TypeInferenceCollector; protected readonly printer: ProblemPrinter; @@ -101,66 +181,128 @@ export class DefaultValidationConstraints implements ValidationCon this.printer = services.Printer; } - ensureNodeIsAssignable( - sourceNode: S | undefined, expected: Type | undefined | E, + ensureNodeIsAssignable< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + expected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider + message: ValidationMessageProvider, ): void { - this.ensureNodeRelatedWithType(sourceNode, expected, 'ASSIGNABLE_TYPE', false, accept, message); + this.ensureNodeRelatedWithType( + sourceNode, + expected, + "ASSIGNABLE_TYPE", + false, + accept, + message, + ); } - ensureNodeIsEquals( - sourceNode: S | undefined, expected: Type | undefined | E, + ensureNodeIsEquals< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + expected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider + message: ValidationMessageProvider, ): void { - this.ensureNodeRelatedWithType(sourceNode, expected, 'EQUAL_TYPE', false, accept, message); + this.ensureNodeRelatedWithType( + sourceNode, + expected, + "EQUAL_TYPE", + false, + accept, + message, + ); } - ensureNodeHasNotType( - sourceNode: S | undefined, notExpected: Type | undefined | E, + ensureNodeHasNotType< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + sourceNode: S | undefined, + notExpected: Type | undefined | E, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider + message: ValidationMessageProvider, ): void { - this.ensureNodeRelatedWithType(sourceNode, notExpected, 'EQUAL_TYPE', true, accept, message); + this.ensureNodeRelatedWithType( + sourceNode, + notExpected, + "EQUAL_TYPE", + true, + accept, + message, + ); } - ensureNodeRelatedWithType( - languageNode: S | undefined, expected: Type | undefined | E, - strategy: TypeCheckStrategy, negated: boolean, + ensureNodeRelatedWithType< + S extends LanguageType, + E extends LanguageType, + T extends LanguageType = LanguageType, + >( + languageNode: S | undefined, + expected: Type | undefined | E, + strategy: TypeCheckStrategy, + negated: boolean, accept: ValidationProblemAcceptor, - message: ValidationMessageProvider + message: ValidationMessageProvider, ): void { if (languageNode !== undefined && expected !== undefined) { - const actualType = isType(languageNode) ? languageNode : this.inference.inferType(languageNode); - const expectedType = isType(expected) ? expected : this.inference.inferType(expected); + const actualType = isType(languageNode) + ? languageNode + : this.inference.inferType(languageNode); + const expectedType = isType(expected) + ? expected + : this.inference.inferType(expected); if (isType(actualType) && isType(expectedType)) { - const strategyLogic = createTypeCheckStrategy(strategy, this.services); - const comparisonResult = strategyLogic(actualType, expectedType); + const strategyLogic = createTypeCheckStrategy( + strategy, + this.services, + ); + const comparisonResult = strategyLogic( + actualType, + expectedType, + ); if (comparisonResult !== undefined) { if (negated) { // everything is fine } else { - const details = message(this.annotateType(actualType), this.annotateType(expectedType)); + const details = message( + this.annotateType(actualType), + this.annotateType(expectedType), + ); accept({ languageNode: details.languageNode ?? languageNode, languageProperty: details.languageProperty, languageIndex: details.languageIndex, - severity: details.severity ?? 'error', - message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, - subProblems: [comparisonResult] + severity: details.severity ?? "error", + message: + details.message ?? + `'${actualType.getIdentifier()}' is ${negated ? "" : "not "}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, + subProblems: [comparisonResult], }); } } else { if (negated) { - const details = message(this.annotateType(actualType), this.annotateType(expectedType)); + const details = message( + this.annotateType(actualType), + this.annotateType(expectedType), + ); accept({ languageNode: details.languageNode ?? languageNode, languageProperty: details.languageProperty, languageIndex: details.languageIndex, - severity: details.severity ?? 'error', - message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, - subProblems: [] // no sub-problems are available! + severity: details.severity ?? "error", + message: + details.message ?? + `'${actualType.getIdentifier()}' is ${negated ? "" : "not "}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, + subProblems: [], // no sub-problems are available! }); } else { // everything is fine @@ -176,15 +318,20 @@ export class DefaultValidationConstraints implements ValidationCon return { type, userRepresentation: this.printer.printTypeUserRepresentation(type), - name: this.printer.printTypeName(type), + name: this.printer.printTypeName(type), }; } } - export interface ValidationCollectorListener { - onAddedValidationRule(rule: ValidationRule, options: ValidationRuleOptions): void; - onRemovedValidationRule(rule: ValidationRule, options: ValidationRuleOptions): void; + onAddedValidationRule( + rule: ValidationRule, + options: ValidationRuleOptions, + ): void; + onRemovedValidationRule( + rule: ValidationRule, + options: ValidationRuleOptions, + ): void; } export interface ValidationRuleOptions extends RuleOptions { @@ -192,33 +339,57 @@ export interface ValidationRuleOptions extends RuleOptions { } export interface ValidationCollector { - validateBefore(languageNode: LanguageType): Array>; - validate(languageNode: LanguageType): Array>; - validateAfter(languageNode: LanguageType): Array>; + validateBefore( + languageNode: LanguageType, + ): Array>; + validate( + languageNode: LanguageType, + ): Array>; + validateAfter( + languageNode: LanguageType, + ): Array>; /** * Registers a validation rule. * @param rule a new validation rule * @param options some more options to control the handling of the added validation rule */ - addValidationRule(rule: ValidationRule, options?: Partial): void; + addValidationRule( + rule: ValidationRule, + options?: Partial, + ): void; /** * Removes a validation rule. * @param rule the validation rule to remove * @param options the same options as given for the registration of the validation rule must be given for the removal! */ - removeValidationRule(rule: ValidationRule, options?: Partial): void; + removeValidationRule( + rule: ValidationRule, + options?: Partial, + ): void; addListener(listener: ValidationCollectorListener): void; removeListener(listener: ValidationCollectorListener): void; } -export class DefaultValidationCollector implements ValidationCollector, RuleCollectorListener> { +export class DefaultValidationCollector + implements + ValidationCollector, + RuleCollectorListener> +{ protected readonly services: TypirServices; - protected readonly listeners: Array> = []; - - protected readonly ruleRegistryFunctional: RuleRegistry, LanguageType>; - protected readonly ruleRegistryLifecycle: RuleRegistry, LanguageType>; + protected readonly listeners: Array< + ValidationCollectorListener + > = []; + + protected readonly ruleRegistryFunctional: RuleRegistry< + ValidationRuleFunctional, + LanguageType + >; + protected readonly ruleRegistryLifecycle: RuleRegistry< + ValidationRuleLifecycle, + LanguageType + >; constructor(services: TypirServices) { this.services = services; @@ -230,8 +401,12 @@ export class DefaultValidationCollector implements ValidationColle this.ruleRegistryLifecycle.addListener(this); } - protected createAcceptor(problems: Array>): ValidationProblemAcceptor { - return (problem: ReducedValidationProblem) => { + protected createAcceptor( + problems: Array>, + ): ValidationProblemAcceptor { + return ( + problem: ReducedValidationProblem, + ) => { problems.push({ ...problem, $problem: ValidationProblem, // add the missing $property-property @@ -239,34 +414,51 @@ export class DefaultValidationCollector implements ValidationColle }; } - validateBefore(languageRoot: LanguageType): Array> { + validateBefore( + languageRoot: LanguageType, + ): Array> { const problems: Array> = []; const accept = this.createAcceptor(problems); - for (const rule of this.ruleRegistryLifecycle.getUniqueRules()) { // the returned rules are unique - rule.beforeValidation?.call(rule, languageRoot, accept, this.services); + for (const rule of this.ruleRegistryLifecycle.getUniqueRules()) { + // the returned rules are unique + rule.beforeValidation?.call( + rule, + languageRoot, + accept, + this.services, + ); } return problems; } - validate(languageNode: LanguageType): Array> { + validate( + languageNode: LanguageType, + ): Array> { // determine all keys to check - const keysToApply: Array = []; - const languageKey = this.services.Language.getLanguageNodeKey(languageNode); + const keysToApply: Array = []; + const languageKey = + this.services.Language.getLanguageNodeKey(languageNode); if (languageKey === undefined) { keysToApply.push(undefined); } else { keysToApply.push(languageKey); // execute the rules which are associated to the key of the current language node - keysToApply.push(...this.services.Language.getAllSuperKeys(languageKey)); // apply all rules which are associated to super-keys + keysToApply.push( + ...this.services.Language.getAllSuperKeys(languageKey), + ); // apply all rules which are associated to super-keys keysToApply.push(undefined); // rules associated with 'undefined' are applied to all language nodes, apply these rules at the end } // execute all rules wich are associated to the relevant language keys const problems: Array> = []; const accept = this.createAcceptor(problems); - const alreadyExecutedRules: Set> = new Set(); // don't execute rules multiple times, if they are associated with multiple keys (with overlapping sub-keys) + const alreadyExecutedRules: Set< + ValidationRuleFunctional + > = new Set(); // don't execute rules multiple times, if they are associated with multiple keys (with overlapping sub-keys) for (const key of keysToApply) { // state-less rules - for (const ruleStateless of this.ruleRegistryFunctional.getRulesByLanguageKey(key)) { + for (const ruleStateless of this.ruleRegistryFunctional.getRulesByLanguageKey( + key, + )) { if (alreadyExecutedRules.has(ruleStateless)) { // don't execute this rule again } else { @@ -276,11 +468,18 @@ export class DefaultValidationCollector implements ValidationColle } // rules with before and after - for (const ruleStateless of this.ruleRegistryLifecycle.getRulesByLanguageKey(key)) { + for (const ruleStateless of this.ruleRegistryLifecycle.getRulesByLanguageKey( + key, + )) { if (alreadyExecutedRules.has(ruleStateless.validation)) { // don't execute this rule again } else { - ruleStateless.validation.call(ruleStateless, languageNode, accept, this.services); + ruleStateless.validation.call( + ruleStateless, + languageNode, + accept, + this.services, + ); alreadyExecutedRules.add(ruleStateless.validation); } } @@ -288,28 +487,54 @@ export class DefaultValidationCollector implements ValidationColle return problems; } - validateAfter(languageRoot: LanguageType): Array> { + validateAfter( + languageRoot: LanguageType, + ): Array> { const problems: Array> = []; const accept = this.createAcceptor(problems); - for (const rule of this.ruleRegistryLifecycle.getUniqueRules()) { // the returned rules are unique - rule.afterValidation?.call(rule, languageRoot, accept, this.services); + for (const rule of this.ruleRegistryLifecycle.getUniqueRules()) { + // the returned rules are unique + rule.afterValidation?.call( + rule, + languageRoot, + accept, + this.services, + ); } return problems; } - addValidationRule(rule: ValidationRule, givenOptions?: Partial): void { - if (typeof rule === 'function') { - this.ruleRegistryFunctional.addRule(rule as ValidationRuleFunctional, givenOptions); + addValidationRule( + rule: ValidationRule, + givenOptions?: Partial, + ): void { + if (typeof rule === "function") { + this.ruleRegistryFunctional.addRule( + rule as ValidationRuleFunctional, + givenOptions, + ); } else { - this.ruleRegistryLifecycle.addRule(rule as ValidationRuleLifecycle, givenOptions); + this.ruleRegistryLifecycle.addRule( + rule as ValidationRuleLifecycle, + givenOptions, + ); } } - removeValidationRule(rule: ValidationRule, givenOptions?: Partial): void { - if (typeof rule === 'function') { - this.ruleRegistryFunctional.removeRule(rule as ValidationRuleFunctional, givenOptions); + removeValidationRule( + rule: ValidationRule, + givenOptions?: Partial, + ): void { + if (typeof rule === "function") { + this.ruleRegistryFunctional.removeRule( + rule as ValidationRuleFunctional, + givenOptions, + ); } else { - this.ruleRegistryLifecycle.removeRule(rule as ValidationRuleLifecycle, givenOptions); + this.ruleRegistryLifecycle.removeRule( + rule as ValidationRuleLifecycle, + givenOptions, + ); } } @@ -320,39 +545,69 @@ export class DefaultValidationCollector implements ValidationColle removeFromArray(listener, this.listeners); } - onAddedRule(rule: ValidationRule, diffOptions: RuleOptions): void { + onAddedRule( + rule: ValidationRule, + diffOptions: RuleOptions, + ): void { // listeners of the composite will be notified about all added inner rules - this.listeners.forEach(listener => listener.onAddedValidationRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onAddedValidationRule(rule, diffOptions), + ); } - onRemovedRule(rule: ValidationRule, diffOptions: RuleOptions): void { + onRemovedRule( + rule: ValidationRule, + diffOptions: RuleOptions, + ): void { // listeners of the composite will be notified about all removed inner rules - this.listeners.forEach(listener => listener.onRemovedValidationRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onRemovedValidationRule(rule, diffOptions), + ); } } - -export class CompositeValidationRule extends DefaultValidationCollector implements ValidationRuleLifecycle { +export class CompositeValidationRule + extends DefaultValidationCollector + implements ValidationRuleLifecycle +{ /** The collector for inference rules, at which this composite rule should be registered. */ protected readonly collectorToRegisterThisRule: ValidationCollector; - constructor(services: TypirServices, collectorToRegisterThisRule: ValidationCollector) { + constructor( + services: TypirServices, + collectorToRegisterThisRule: ValidationCollector, + ) { super(services); this.collectorToRegisterThisRule = collectorToRegisterThisRule; } - beforeValidation(languageRoot: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { - this.validateBefore(languageRoot).forEach(v => accept(v)); + beforeValidation( + languageRoot: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + this.validateBefore(languageRoot).forEach((v) => accept(v)); } - validation(languageNode: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { - this.validate(languageNode).forEach(v => accept(v)); + validation( + languageNode: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + this.validate(languageNode).forEach((v) => accept(v)); } - afterValidation(languageRoot: LanguageType, accept: ValidationProblemAcceptor, _typir: TypirServices): void { - this.validateAfter(languageRoot).forEach(v => accept(v)); + afterValidation( + languageRoot: LanguageType, + accept: ValidationProblemAcceptor, + _typir: TypirServices, + ): void { + this.validateAfter(languageRoot).forEach((v) => accept(v)); } - override onAddedRule(rule: ValidationRule, diffOptions: RuleOptions): void { + override onAddedRule( + rule: ValidationRule, + diffOptions: RuleOptions, + ): void { // an inner rule was added super.onAddedRule(rule, diffOptions); @@ -363,13 +618,21 @@ export class CompositeValidationRule extends DefaultValidationColl }); } - override onRemovedRule(rule: ValidationRule, diffOptions: RuleOptions): void { + override onRemovedRule( + rule: ValidationRule, + diffOptions: RuleOptions, + ): void { // an inner rule was removed super.onRemovedRule(rule, diffOptions); // remove this composite rule for all language keys for which no inner rules are registered anymore if (diffOptions.languageKey === undefined) { - if (this.ruleRegistryFunctional.getRulesByLanguageKey(undefined).length <= 0 && this.ruleRegistryLifecycle.getRulesByLanguageKey(undefined).length <= 0) { + if ( + this.ruleRegistryFunctional.getRulesByLanguageKey(undefined) + .length <= 0 && + this.ruleRegistryLifecycle.getRulesByLanguageKey(undefined) + .length <= 0 + ) { this.collectorToRegisterThisRule.removeValidationRule(this, { ...diffOptions, languageKey: undefined, @@ -377,8 +640,15 @@ export class CompositeValidationRule extends DefaultValidationColl }); } } else { - const languageKeysToUnregister = toArray(diffOptions.languageKey) - .filter(key => this.ruleRegistryFunctional.getRulesByLanguageKey(key).length <= 0 && this.ruleRegistryLifecycle.getRulesByLanguageKey(key).length <= 0); + const languageKeysToUnregister = toArray( + diffOptions.languageKey, + ).filter( + (key) => + this.ruleRegistryFunctional.getRulesByLanguageKey(key) + .length <= 0 && + this.ruleRegistryLifecycle.getRulesByLanguageKey(key) + .length <= 0, + ); this.collectorToRegisterThisRule.removeValidationRule(this, { ...diffOptions, languageKey: languageKeysToUnregister, diff --git a/packages/typir/src/test/predefined-language-nodes.ts b/packages/typir/src/test/predefined-language-nodes.ts index 817ba315..21dea93c 100644 --- a/packages/typir/src/test/predefined-language-nodes.ts +++ b/packages/typir/src/test/predefined-language-nodes.ts @@ -4,9 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { DefaultLanguageService } from '../services/language.js'; -import { InferOperatorWithMultipleOperands } from '../services/operator.js'; -import { DefaultTypeConflictPrinter } from '../services/printing.js'; +import { DefaultLanguageService } from "../services/language.js"; +import { InferOperatorWithMultipleOperands } from "../services/operator.js"; +import { DefaultTypeConflictPrinter } from "../services/printing.js"; /* eslint-disable @typescript-eslint/parameter-properties */ @@ -15,7 +15,6 @@ import { DefaultTypeConflictPrinter } from '../services/printing.js'; * which are predefined for test cases. */ export abstract class TestLanguageNode { - constructor() { // empty } @@ -25,13 +24,15 @@ export abstract class TestLanguageNode { const obj = this; const properties = Object.entries(obj) .map((key, value) => `${key}: ${this.printObject(value)}`) - .join(', '); + .join(", "); return `${this.constructor.name}(${properties})`; } protected printObject(obj: unknown): string { if (Array.isArray(obj)) { - const entries = Array.from(obj.values()).map(v => this.printObject(v)).join(', '); + const entries = Array.from(obj.values()) + .map((v) => this.printObject(v)) + .join(", "); return `[${entries}]`; } if (obj instanceof TestLanguageNode) { @@ -39,35 +40,31 @@ export abstract class TestLanguageNode { } return `${obj}`; } - } -export abstract class TestExpressionNode extends TestLanguageNode { -} - -export abstract class TestStatementNode extends TestLanguageNode { -} +export abstract class TestExpressionNode extends TestLanguageNode {} +export abstract class TestStatementNode extends TestLanguageNode {} export class IntegerLiteral extends TestExpressionNode { - constructor( - public value: number, - ) { super(); } + constructor(public value: number) { + super(); + } } export class DoubleLiteral extends TestExpressionNode { - constructor( - public value: number, - ) { super(); } + constructor(public value: number) { + super(); + } } export class BooleanLiteral extends TestExpressionNode { - constructor( - public value: boolean, - ) { super(); } + constructor(public value: boolean) { + super(); + } } export class StringLiteral extends TestExpressionNode { - constructor( - public value: string, - ) { super(); } + constructor(public value: string) { + super(); + } } // some predefined literals @@ -84,81 +81,92 @@ export const double3_0 = new DoubleLiteral(3.0); export const booleanTrue = new BooleanLiteral(true); export const booleanFalse = new BooleanLiteral(false); -export const string123 = new StringLiteral('123'); -export const string456 = new StringLiteral('456'); -export const string2 = new StringLiteral('2'); -export const string3 = new StringLiteral('3'); -export const stringHello = new StringLiteral('Hello'); -export const stringWorld = new StringLiteral('World'); - +export const string123 = new StringLiteral("123"); +export const string456 = new StringLiteral("456"); +export const string2 = new StringLiteral("2"); +export const string3 = new StringLiteral("3"); +export const stringHello = new StringLiteral("Hello"); +export const stringWorld = new StringLiteral("World"); export class ClassConstructorCall extends TestExpressionNode { - constructor( - public className: string, - ) { super(); } + constructor(public className: string) { + super(); + } } export class ClassFieldAccess extends TestExpressionNode { constructor( public classVariable: Variable, public fieldName: string, - ) { super(); } + ) { + super(); + } } - export class BinaryExpression extends TestExpressionNode { constructor( public left: TestExpressionNode, public operator: string, public right: TestExpressionNode, - ) { super(); } + ) { + super(); + } } - export class Variable extends TestLanguageNode { constructor( public name: string, public initialValue: TestExpressionNode, // the type of this initialization expression is used as type of the variable - ) { super(); } + ) { + super(); + } } - export class AssignmentStatement extends TestStatementNode { constructor( public left: Variable, public right: TestExpressionNode, - ) { super(); } + ) { + super(); + } } export class StatementBlock extends TestStatementNode { - constructor( - public statements: TestLanguageNode[], - ) { super(); } + constructor(public statements: TestLanguageNode[]) { + super(); + } } - /* * Some predefined utils for configuring Typir accordingly */ -export const InferenceRuleBinaryExpression: InferOperatorWithMultipleOperands = { - filter: node => node instanceof BinaryExpression, +export const InferenceRuleBinaryExpression: InferOperatorWithMultipleOperands< + TestLanguageNode, + BinaryExpression +> = { + filter: (node) => node instanceof BinaryExpression, matching: (node, operatorName) => node.operator === operatorName, - operands: node => [node.left, node.right], + operands: (node) => [node.left, node.right], validateArgumentsOfCalls: true, }; export class TestProblemPrinter extends DefaultTypeConflictPrinter { - override printLanguageNode(languageNode: TestLanguageNode, sentenceBegin?: boolean | undefined): string { + override printLanguageNode( + languageNode: TestLanguageNode, + sentenceBegin?: boolean | undefined, + ): string { if (languageNode instanceof TestLanguageNode) { - return `${sentenceBegin ? 'T' : 't'}he language node '${languageNode.print()}'`; + return `${sentenceBegin ? "T" : "t"}he language node '${languageNode.print()}'`; } return super.printLanguageNode(languageNode, sentenceBegin); } } export class TestLanguageService extends DefaultLanguageService { - override getLanguageNodeKey(languageNode: TestLanguageNode): string | undefined { + override getLanguageNodeKey( + languageNode: TestLanguageNode, + ): string | undefined { return languageNode.constructor.name; } } diff --git a/packages/typir/src/typir.ts b/packages/typir/src/typir.ts index 8e2d2366..e4a3439a 100644 --- a/packages/typir/src/typir.ts +++ b/packages/typir/src/typir.ts @@ -4,26 +4,80 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { DefaultGraphAlgorithms, GraphAlgorithms } from './graph/graph-algorithms.js'; -import { TypeGraph } from './graph/type-graph.js'; -import { DefaultTypeResolver, TypeResolvingService } from './initialization/type-selector.js'; -import { BottomFactoryService, BottomKind, BottomKindName } from './kinds/bottom/bottom-kind.js'; -import { ClassFactoryService, ClassKind, ClassKindName } from './kinds/class/class-kind.js'; -import { FunctionFactoryService, FunctionKind, FunctionKindName } from './kinds/function/function-kind.js'; -import { PrimitiveFactoryService, PrimitiveKind, PrimitiveKindName } from './kinds/primitive/primitive-kind.js'; -import { TopFactoryService, TopKind, TopKindName } from './kinds/top/top-kind.js'; -import { DefaultTypeAssignability, TypeAssignability } from './services/assignability.js'; -import { DefaultLanguageNodeInferenceCaching, DefaultTypeRelationshipCaching, LanguageNodeInferenceCaching, TypeRelationshipCaching } from './services/caching.js'; -import { DefaultTypeConversion, TypeConversion } from './services/conversion.js'; -import { DefaultTypeEquality, TypeEquality } from './services/equality.js'; -import { DefaultTypeInferenceCollector, TypeInferenceCollector } from './services/inference.js'; -import { DefaultKindRegistry, KindRegistry } from './services/kind-registry.js'; -import { DefaultLanguageService, LanguageService } from './services/language.js'; -import { DefaultOperatorFactory, OperatorFactoryService } from './services/operator.js'; -import { DefaultTypeConflictPrinter, ProblemPrinter } from './services/printing.js'; -import { DefaultSubType, SubType } from './services/subtype.js'; -import { DefaultValidationCollector, DefaultValidationConstraints, ValidationCollector, ValidationConstraints } from './services/validation.js'; -import { inject, Module } from './utils/dependency-injection.js'; +import { + DefaultGraphAlgorithms, + GraphAlgorithms, +} from "./graph/graph-algorithms.js"; +import { TypeGraph } from "./graph/type-graph.js"; +import { + DefaultTypeResolver, + TypeResolvingService, +} from "./initialization/type-selector.js"; +import { + BottomFactoryService, + BottomKind, + BottomKindName, +} from "./kinds/bottom/bottom-kind.js"; +import { + ClassFactoryService, + ClassKind, + ClassKindName, +} from "./kinds/class/class-kind.js"; +import { + FunctionFactoryService, + FunctionKind, + FunctionKindName, +} from "./kinds/function/function-kind.js"; +import { + PrimitiveFactoryService, + PrimitiveKind, + PrimitiveKindName, +} from "./kinds/primitive/primitive-kind.js"; +import { + TopFactoryService, + TopKind, + TopKindName, +} from "./kinds/top/top-kind.js"; +import { + DefaultTypeAssignability, + TypeAssignability, +} from "./services/assignability.js"; +import { + DefaultLanguageNodeInferenceCaching, + DefaultTypeRelationshipCaching, + LanguageNodeInferenceCaching, + TypeRelationshipCaching, +} from "./services/caching.js"; +import { + DefaultTypeConversion, + TypeConversion, +} from "./services/conversion.js"; +import { DefaultTypeEquality, TypeEquality } from "./services/equality.js"; +import { + DefaultTypeInferenceCollector, + TypeInferenceCollector, +} from "./services/inference.js"; +import { DefaultKindRegistry, KindRegistry } from "./services/kind-registry.js"; +import { + DefaultLanguageService, + LanguageService, +} from "./services/language.js"; +import { + DefaultOperatorFactory, + OperatorFactoryService, +} from "./services/operator.js"; +import { + DefaultTypeConflictPrinter, + ProblemPrinter, +} from "./services/printing.js"; +import { DefaultSubType, SubType } from "./services/subtype.js"; +import { + DefaultValidationCollector, + DefaultValidationConstraints, + ValidationCollector, + ValidationConstraints, +} from "./services/validation.js"; +import { inject, Module } from "./utils/dependency-injection.js"; /** * Some design decisions for Typir: @@ -73,7 +127,9 @@ export type TypirServices = { }; }; -export function createDefaultTypirServicesModule(): Module> { +export function createDefaultTypirServicesModule(): Module< + TypirServices +> { return { Assignability: (services) => new DefaultTypeAssignability(services), Equality: (services) => new DefaultTypeEquality(services), @@ -81,21 +137,45 @@ export function createDefaultTypirServicesModule(): Module new DefaultSubType(services), Inference: (services) => new DefaultTypeInferenceCollector(services), caching: { - TypeRelationships: (services) => new DefaultTypeRelationshipCaching(services), - LanguageNodeInference: () => new DefaultLanguageNodeInferenceCaching(), + TypeRelationships: (services) => + new DefaultTypeRelationshipCaching(services), + LanguageNodeInference: () => + new DefaultLanguageNodeInferenceCaching(), }, Printer: () => new DefaultTypeConflictPrinter(), Language: () => new DefaultLanguageService(), validation: { Collector: (services) => new DefaultValidationCollector(services), - Constraints: (services) => new DefaultValidationConstraints(services), + Constraints: (services) => + new DefaultValidationConstraints(services), }, factory: { - Primitives: (services) => services.infrastructure.Kinds.getOrCreateKind(PrimitiveKindName, services => new PrimitiveKind(services)), - Functions: (services) => services.infrastructure.Kinds.getOrCreateKind(FunctionKindName, services => new FunctionKind(services)), - Classes: (services) => services.infrastructure.Kinds.getOrCreateKind(ClassKindName, services => new ClassKind(services, { typing: 'Nominal' })), - Top: (services) => services.infrastructure.Kinds.getOrCreateKind(TopKindName, services => new TopKind(services)), - Bottom: (services) => services.infrastructure.Kinds.getOrCreateKind(BottomKindName, services => new BottomKind(services)), + Primitives: (services) => + services.infrastructure.Kinds.getOrCreateKind( + PrimitiveKindName, + (services) => new PrimitiveKind(services), + ), + Functions: (services) => + services.infrastructure.Kinds.getOrCreateKind( + FunctionKindName, + (services) => new FunctionKind(services), + ), + Classes: (services) => + services.infrastructure.Kinds.getOrCreateKind( + ClassKindName, + (services) => + new ClassKind(services, { typing: "Nominal" }), + ), + Top: (services) => + services.infrastructure.Kinds.getOrCreateKind( + TopKindName, + (services) => new TopKind(services), + ), + Bottom: (services) => + services.infrastructure.Kinds.getOrCreateKind( + BottomKindName, + (services) => new BottomKind(services), + ), Operators: (services) => new DefaultOperatorFactory(services), }, infrastructure: { @@ -115,11 +195,25 @@ export function createDefaultTypirServicesModule(): Module( - customization1: Module, PartialTypirServices> = {}, - customization2: Module, PartialTypirServices> = {}, - customization3: Module, PartialTypirServices> = {}, + customization1: Module< + TypirServices, + PartialTypirServices + > = {}, + customization2: Module< + TypirServices, + PartialTypirServices + > = {}, + customization3: Module< + TypirServices, + PartialTypirServices + > = {}, ): TypirServices { - return inject(createDefaultTypirServicesModule(), customization1, customization2, customization3); + return inject( + createDefaultTypirServicesModule(), + customization1, + customization2, + customization3, + ); } /** @@ -128,11 +222,15 @@ export function createTypirServices( * Copied from Langium. */ //eslint-disable-next-line @typescript-eslint/ban-types -export type DeepPartial = T[keyof T] extends Function ? T : { - [P in keyof T]?: DeepPartial; -} +export type DeepPartial = T[keyof T] extends Function + ? T + : { + [P in keyof T]?: DeepPartial; + }; /** * Language-specific services to be partially overridden via dependency injection. */ -export type PartialTypirServices = DeepPartial> +export type PartialTypirServices = DeepPartial< + TypirServices +>; diff --git a/packages/typir/src/utils/dependency-injection.ts b/packages/typir/src/utils/dependency-injection.ts index ff3ef70f..77789e49 100644 --- a/packages/typir/src/utils/dependency-injection.ts +++ b/packages/typir/src/utils/dependency-injection.ts @@ -16,11 +16,14 @@ * dependencies. */ export type Module = { - [K in keyof T]: Module | ((injector: I) => T[K]) -} + [K in keyof T]: Module | ((injector: I) => T[K]); +}; export namespace Module { - export const merge = (m1: Module, m2: Module) => (_merge(_merge({}, m1), m2) as Module); + export const merge = ( + m1: Module, + m2: Module, + ) => _merge(_merge({}, m1), m2) as Module; } /** @@ -45,14 +48,43 @@ export namespace Module { * @param module9 (optional) ninth Module * @returns a new object of type I */ -export function inject( - module1: Module, module2?: Module, module3?: Module, module4?: Module, module5?: Module, module6?: Module, module7?: Module, module8?: Module, module9?: Module +export function inject< + I1, + I2, + I3, + I4, + I5, + I6, + I7, + I8, + I9, + I extends I1 & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, +>( + module1: Module, + module2?: Module, + module3?: Module, + module4?: Module, + module5?: Module, + module6?: Module, + module7?: Module, + module8?: Module, + module9?: Module, ): I { - const module = [module1, module2, module3, module4, module5, module6, module7, module8, module9].reduce(_merge, {}) as Module; + const module = [ + module1, + module2, + module3, + module4, + module5, + module6, + module7, + module8, + module9, + ].reduce(_merge, {}) as Module; return _inject(module); } -const isProxy = Symbol('isProxy'); +const isProxy = Symbol("isProxy"); /** * Eagerly load all services in the given dependency injection container. This is sometimes @@ -75,7 +107,9 @@ function _inject(module: Module, injector?: any): T { const proxy: any = new Proxy({} as any, { deleteProperty: () => false, set: () => { - throw new Error('Cannot set property on injected service container'); + throw new Error( + "Cannot set property on injected service container", + ); }, get: (obj, prop) => { if (prop === isProxy) { @@ -84,9 +118,12 @@ function _inject(module: Module, injector?: any): T { return _resolve(obj, prop, module, injector || proxy); } }, - getOwnPropertyDescriptor: (obj, prop) => (_resolve(obj, prop, module, injector || proxy), Object.getOwnPropertyDescriptor(obj, prop)), // used by for..in + getOwnPropertyDescriptor: (obj, prop) => ( + _resolve(obj, prop, module, injector || proxy), + Object.getOwnPropertyDescriptor(obj, prop) + ), // used by for..in has: (_, prop) => prop in module, // used by ..in.. - ownKeys: () => [...Object.getOwnPropertyNames(module)] // used by for..in + ownKeys: () => [...Object.getOwnPropertyNames(module)], // used by for..in }); return proxy; } @@ -109,20 +146,36 @@ const __requested__ = Symbol(); * @returns the requested value `obj[prop]` * @throws Error if a dependency cycle is detected */ -function _resolve(obj: any, prop: string | symbol | number, module: Module, injector: I): T[keyof T] | undefined { +function _resolve( + obj: any, + prop: string | symbol | number, + module: Module, + injector: I, +): T[keyof T] | undefined { if (prop in obj) { if (obj[prop] instanceof Error) { - throw new Error('Construction failure. Please make sure that your dependencies are constructable.', {cause: obj[prop]}); + throw new Error( + "Construction failure. Please make sure that your dependencies are constructable.", + { cause: obj[prop] }, + ); } if (obj[prop] === __requested__) { - throw new Error('Cycle detected. Please make "' + String(prop) + '" lazy. Visit https://langium.org/docs/reference/configuration-services/#resolving-cyclic-dependencies'); + throw new Error( + 'Cycle detected. Please make "' + + String(prop) + + '" lazy. Visit https://langium.org/docs/reference/configuration-services/#resolving-cyclic-dependencies', + ); } return obj[prop]; } else if (prop in module) { - const value: Module | ((injector: I) => T[keyof T]) = module[prop as keyof T]; + const value: Module | ((injector: I) => T[keyof T]) = + module[prop as keyof T]; obj[prop] = __requested__; try { - obj[prop] = (typeof value === 'function') ? value(injector) : _inject(value, injector); + obj[prop] = + typeof value === "function" + ? value(injector) + : _inject(value, injector); } catch (error) { obj[prop] = error instanceof Error ? error : undefined; throw error; @@ -145,7 +198,12 @@ function _merge(target: Module, source?: Module): Module { for (const [key, value2] of Object.entries(source)) { if (value2 !== undefined) { const value1 = target[key]; - if (value1 !== null && value2 !== null && typeof value1 === 'object' && typeof value2 === 'object') { + if ( + value1 !== null && + value2 !== null && + typeof value1 === "object" && + typeof value2 === "object" + ) { target[key] = _merge(value1, value2); } else { target[key] = value2; diff --git a/packages/typir/src/utils/rule-registration.ts b/packages/typir/src/utils/rule-registration.ts index 54137901..46ba3342 100644 --- a/packages/typir/src/utils/rule-registration.ts +++ b/packages/typir/src/utils/rule-registration.ts @@ -2,12 +2,12 @@ * Copyright 2025 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ + ******************************************************************************/ -import { TypeGraphListener } from '../graph/type-graph.js'; -import { Type } from '../graph/type-node.js'; -import { TypirServices } from '../typir.js'; -import { removeFromArray, toArray, toArrayWithValue } from './utils.js'; +import { TypeGraphListener } from "../graph/type-graph.js"; +import { Type } from "../graph/type-node.js"; +import { TypirServices } from "../typir.js"; +import { removeFromArray, toArray, toArrayWithValue } from "./utils.js"; export interface RuleOptions { /** @@ -44,7 +44,10 @@ export class RuleRegistry implements TypeGraphListener { * language node type --> rules * Improves the look-up of related rules, when doing type for a concrete language node. * All rules are registered at least once in this map, since rules without dedicated language key are registered to 'undefined'. */ - protected readonly languageTypeToRules: Map = new Map(); + protected readonly languageTypeToRules: Map< + string | undefined, + RuleType[] + > = new Map(); /** * type identifier --> -> rules * Improves the look-up for rules which are bound to types, when these types are removed. @@ -53,14 +56,14 @@ export class RuleRegistry implements TypeGraphListener { /** * rule --> its collected options * Contains the current set of all options for an rule. */ - protected readonly ruleToOptions: Map = new Map(); + protected readonly ruleToOptions: Map = + new Map(); /** Collects all unique rules, lazily managed. */ protected readonly uniqueRules: Set = new Set(); protected readonly listeners: Array> = []; - constructor(services: TypirServices) { services.infrastructure.Graph.addListener(this); } @@ -77,7 +80,9 @@ export class RuleRegistry implements TypeGraphListener { getUniqueRules(): Set { if (this.uniqueRules.size <= 0) { // lazily fill the set of unique rules - Array.from(this.languageTypeToRules.values()).flatMap(v => v).forEach(v => this.uniqueRules.add(v)); + Array.from(this.languageTypeToRules.values()) + .flatMap((v) => v) + .forEach((v) => this.uniqueRules.add(v)); } return this.uniqueRules; } @@ -102,7 +107,8 @@ export class RuleRegistry implements TypeGraphListener { addRule(rule: RuleType, givenOptions?: Partial): void { const newOptions = this.getRuleOptions(givenOptions); - const languageKeyUndefined: boolean = newOptions.languageKey === undefined; + const languageKeyUndefined: boolean = + newOptions.languageKey === undefined; const languageKeys: string[] = toArray(newOptions.languageKey); const existingOptions = this.ruleToOptions.get(rule); @@ -120,7 +126,9 @@ export class RuleRegistry implements TypeGraphListener { // nothing to do, since this rule is already registered for 'undefined' } else { // since the rule shall be registered for 'undefined', remove all existing specific language keys - this.removeRule(rule, { languageKey: existingOptions?.languageKeys ?? [] }); + this.removeRule(rule, { + languageKey: existingOptions?.languageKeys ?? [], + }); // register this rule for 'undefined' let rules = this.languageTypeToRules.get(undefined); @@ -151,7 +159,10 @@ export class RuleRegistry implements TypeGraphListener { // this rule is unknown until now rules.push(rule); added = true; - diffOptions.languageKey = toArrayWithValue(key, diffOptions.languageKey); + diffOptions.languageKey = toArrayWithValue( + key, + diffOptions.languageKey, + ); } else { if (existingOptions.languageKeys.includes(key)) { // this rule is already registered with this language key => do nothing @@ -160,7 +171,10 @@ export class RuleRegistry implements TypeGraphListener { rules.push(rule); existingOptions.languageKeys.push(key); added = true; - diffOptions.languageKey = toArrayWithValue(key, diffOptions.languageKey); + diffOptions.languageKey = toArrayWithValue( + key, + diffOptions.languageKey, + ); } } } @@ -178,7 +192,10 @@ export class RuleRegistry implements TypeGraphListener { if (existingOptions === undefined) { // this rule is unknown until now rules.push(rule); - diffOptions.boundToType = toArrayWithValue(boundToType, diffOptions.boundToType); + diffOptions.boundToType = toArrayWithValue( + boundToType, + diffOptions.boundToType, + ); added = true; } else { if (existingOptions.boundToTypes.includes(boundToType)) { @@ -187,7 +204,10 @@ export class RuleRegistry implements TypeGraphListener { // this rule is known, but not bound to the current type yet existingOptions.boundToTypes.push(boundToType); rules.push(rule); - diffOptions.boundToType = toArrayWithValue(boundToType, diffOptions.boundToType); + diffOptions.boundToType = toArrayWithValue( + boundToType, + diffOptions.boundToType, + ); added = true; } } @@ -198,7 +218,9 @@ export class RuleRegistry implements TypeGraphListener { this.ruleToOptions.set(rule, { languageKeyUndefined: languageKeyUndefined, languageKeys: languageKeys, - boundToTypes: toArray(newOptions.boundToType, { newArray: true }), + boundToTypes: toArray(newOptions.boundToType, { + newArray: true, + }), }); } else { // the existing options are already updated above @@ -213,18 +235,25 @@ export class RuleRegistry implements TypeGraphListener { // inform all listeners about the new rule if (added) { - this.listeners.forEach(listener => listener.onAddedRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onAddedRule(rule, diffOptions), + ); } } removeRule(rule: RuleType, optionsToRemove?: Partial): void { const existingOptions = this.ruleToOptions.get(rule); - if (existingOptions === undefined) { // these options need to be updated (or completely removed at the end) + if (existingOptions === undefined) { + // these options need to be updated (or completely removed at the end) return; // the rule is unknown here => nothing to do } - const languageKeyUndefined: boolean = optionsToRemove ? (optionsToRemove.languageKey === undefined) : true; - const languageKeys: string[] = toArray(optionsToRemove?.languageKey, { newArray: true }); + const languageKeyUndefined: boolean = optionsToRemove + ? optionsToRemove.languageKey === undefined + : true; + const languageKeys: string[] = toArray(optionsToRemove?.languageKey, { + newArray: true, + }); const diffOptions: RuleOptions = { // ... maybe more options in the future ... @@ -237,7 +266,10 @@ export class RuleRegistry implements TypeGraphListener { if (languageKeyUndefined) { // deregister the rule for 'undefined' if (existingOptions.languageKeyUndefined) { - const result = this.deregisterRuleForLanguageKey(rule, undefined); + const result = this.deregisterRuleForLanguageKey( + rule, + undefined, + ); if (result) { removed = true; diffOptions.languageKey = undefined; @@ -254,14 +286,23 @@ export class RuleRegistry implements TypeGraphListener { // since the rule is registered for 'undefined', i.e. all language keys, don't remove some language keys here } else { for (const key of languageKeys) { - const result1 = this.deregisterRuleForLanguageKey(rule, key); - const result2 = removeFromArray(key, existingOptions.languageKeys); // update existing options + const result1 = this.deregisterRuleForLanguageKey( + rule, + key, + ); + const result2 = removeFromArray( + key, + existingOptions.languageKeys, + ); // update existing options if (result1 !== result2) { throw new Error(); } if (result1) { removed = true; - diffOptions.languageKey = toArrayWithValue(key, diffOptions.languageKey); + diffOptions.languageKey = toArrayWithValue( + key, + diffOptions.languageKey, + ); } } } @@ -275,9 +316,13 @@ export class RuleRegistry implements TypeGraphListener { const result = removeFromArray(rule, rules); if (result) { removed = true; - diffOptions.boundToType = toArrayWithValue(boundToType, diffOptions.boundToType); - removeFromArray(boundToType , existingOptions.boundToTypes); // update existing options - if (rules.length <= 0) { // remove empty entries + diffOptions.boundToType = toArrayWithValue( + boundToType, + diffOptions.boundToType, + ); + removeFromArray(boundToType, existingOptions.boundToTypes); // update existing options + if (rules.length <= 0) { + // remove empty entries this.typirTypeToRules.delete(typeKey); } } @@ -285,7 +330,10 @@ export class RuleRegistry implements TypeGraphListener { } // if the rule is not relevant anymore, clear the options map - if (existingOptions.languageKeyUndefined === false && existingOptions.languageKeys.length <= 0) { + if ( + existingOptions.languageKeyUndefined === false && + existingOptions.languageKeys.length <= 0 + ) { this.ruleToOptions.delete(rule); } @@ -294,15 +342,21 @@ export class RuleRegistry implements TypeGraphListener { // inform listeners if (removed) { - this.listeners.forEach(listener => listener.onRemovedRule(rule, diffOptions)); + this.listeners.forEach((listener) => + listener.onRemovedRule(rule, diffOptions), + ); } } - protected deregisterRuleForLanguageKey(rule: RuleType, languageKey: string | undefined): boolean { + protected deregisterRuleForLanguageKey( + rule: RuleType, + languageKey: string | undefined, + ): boolean { const rules = this.languageTypeToRules.get(languageKey); if (rules) { const result = removeFromArray(rule, rules); - if (rules.length <= 0) { // remove empty entries + if (rules.length <= 0) { + // remove empty entries this.languageTypeToRules.delete(languageKey); } return result; @@ -311,7 +365,7 @@ export class RuleRegistry implements TypeGraphListener { } protected getBoundToTypeKey(boundToType?: Type): string { - return boundToType?.getIdentifier() ?? ''; + return boundToType?.getIdentifier() ?? ""; } /* Get informed about deleted types in order to remove rules which are bound to them. */ @@ -325,26 +379,36 @@ export class RuleRegistry implements TypeGraphListener { // for each rule which was bound to the removed type: for (const ruleToRemove of entriesToRemove) { const existingOptions = this.ruleToOptions.get(ruleToRemove)!; - const removed = removeFromArray(type, existingOptions.boundToTypes); + const removed = removeFromArray( + type, + existingOptions.boundToTypes, + ); if (removed) { if (existingOptions.boundToTypes.length <= 0) { // this rule is not bound to any existing type anymore => remove this rule completely this.removeRule(ruleToRemove, { // ... maybe additional properties in the future? // boundToType: there are no bounded types anymore! - languageKey: existingOptions.languageKeyUndefined ? undefined : existingOptions.languageKeys, + languageKey: existingOptions.languageKeyUndefined + ? undefined + : existingOptions.languageKeys, }); } else { // inform listeners about removed rules - this.listeners.forEach(listener => listener.onRemovedRule(ruleToRemove, { - ...existingOptions, - languageKey: existingOptions.languageKeyUndefined ? undefined : existingOptions.languageKeys, - boundToType: type, - // Note that more future options might be unknown here ... (let's hope, they are not relevant here) - })); + this.listeners.forEach((listener) => + listener.onRemovedRule(ruleToRemove, { + ...existingOptions, + languageKey: + existingOptions.languageKeyUndefined + ? undefined + : existingOptions.languageKeys, + boundToType: type, + // Note that more future options might be unknown here ... (let's hope, they are not relevant here) + }), + ); } } else { - throw new Error('Removed type does not exist here'); + throw new Error("Removed type does not exist here"); } } } diff --git a/packages/typir/src/utils/test-utils.ts b/packages/typir/src/utils/test-utils.ts index 37cfbe8c..a4121351 100644 --- a/packages/typir/src/utils/test-utils.ts +++ b/packages/typir/src/utils/test-utils.ts @@ -4,12 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { expect } from 'vitest'; -import { Type } from '../graph/type-node.js'; -import { TestLanguageNode, TestLanguageService, TestProblemPrinter } from '../test/predefined-language-nodes.js'; -import { createDefaultTypirServicesModule, createTypirServices, PartialTypirServices, TypirServices } from '../typir.js'; -import { Module } from './dependency-injection.js'; -import { Severity } from '../services/validation.js'; +import { expect } from "vitest"; +import { Type } from "../graph/type-node.js"; +import { + TestLanguageNode, + TestLanguageService, + TestProblemPrinter, +} from "../test/predefined-language-nodes.js"; +import { + createDefaultTypirServicesModule, + createTypirServices, + PartialTypirServices, + TypirServices, +} from "../typir.js"; +import { Module } from "./dependency-injection.js"; +import { Severity } from "../services/validation.js"; /** * Testing utility to check, that exactly the expected types are in the type system. @@ -20,33 +29,52 @@ import { Severity } from '../services/validation.js'; * it is possible to specify names multiple times, if there are multiple types with the same name (e.g. for overloaded functions) * @returns all the found types */ -export function expectTypirTypes(services: TypirServices, filterTypes: (type: Type) => boolean, ...namesOfExpectedTypes: string[]): Type[] { - const types = services.infrastructure.Graph.getAllRegisteredTypes().filter(filterTypes); - types.forEach(type => expect(type.getInitializationState()).toBe('Completed')); // check that all types are 'Completed' - const typeNames = types.map(t => t.getName()); - expect(typeNames, typeNames.join(', ')).toHaveLength(namesOfExpectedTypes.length); +export function expectTypirTypes( + services: TypirServices, + filterTypes: (type: Type) => boolean, + ...namesOfExpectedTypes: string[] +): Type[] { + const types = + services.infrastructure.Graph.getAllRegisteredTypes().filter( + filterTypes, + ); + types.forEach((type) => + expect(type.getInitializationState()).toBe("Completed"), + ); // check that all types are 'Completed' + const typeNames = types.map((t) => t.getName()); + expect(typeNames, typeNames.join(", ")).toHaveLength( + namesOfExpectedTypes.length, + ); for (const name of namesOfExpectedTypes) { const index = typeNames.indexOf(name); expect(index >= 0).toBeTruthy(); typeNames.splice(index, 1); // removing elements is needed to work correctly with duplicated entries } - expect(typeNames, `There are more types than expected: ${typeNames.join(', ')}`).toHaveLength(0); + expect( + typeNames, + `There are more types than expected: ${typeNames.join(", ")}`, + ).toHaveLength(0); return types; } -export function expectToBeType(type: unknown, checkType: (t: unknown) => t is T, checkDetails: (t: T) => boolean): void { +export function expectToBeType( + type: unknown, + checkType: (t: unknown) => t is T, + checkDetails: (t: T) => boolean, +): void { if (checkType(type)) { if (checkDetails(type)) { // everything is fine } else { - expect.fail(`'${type.getIdentifier()}' is the actual Typir type, but the details are wrong`); + expect.fail( + `'${type.getIdentifier()}' is the actual Typir type, but the details are wrong`, + ); } } else { expect.fail(`'${type}' is not the expected Typir type`); } } - /** * Tests, whether exactly the specified issues are found during the validation of the given language node, * i.e. neither more nor less validation issues. @@ -54,7 +82,11 @@ export function expectToBeType(type: unknown, checkType: (t: unk * @param languageNode the language node to validate * @param expectedIssues the expected issues to occur */ -export function expectValidationIssues(services: TypirServices, languageNode: LanguageType, expectedIssues: string[]): void; +export function expectValidationIssues( + services: TypirServices, + languageNode: LanguageType, + expectedIssues: string[], +): void; /** * Tests, whether the specified issues are found during the validation of the given language node, * more validation issues beyond the specified ones might occur. @@ -63,9 +95,21 @@ export function expectValidationIssues(services: TypirServices(services: TypirServices, languageNode: LanguageType, options: ExpectedValidationIssuesOptions, expectedIssues: string[]): void; -export function expectValidationIssues(services: TypirServices, languageNode: LanguageType, optionsOrIssues: ExpectedValidationIssuesOptions | string[], issues?: string[]): void { - const expectedIssues = Array.isArray(optionsOrIssues) ? optionsOrIssues : issues ?? []; +export function expectValidationIssues( + services: TypirServices, + languageNode: LanguageType, + options: ExpectedValidationIssuesOptions, + expectedIssues: string[], +): void; +export function expectValidationIssues( + services: TypirServices, + languageNode: LanguageType, + optionsOrIssues: ExpectedValidationIssuesOptions | string[], + issues?: string[], +): void { + const expectedIssues = Array.isArray(optionsOrIssues) + ? optionsOrIssues + : (issues ?? []); const options = Array.isArray(optionsOrIssues) ? {} : optionsOrIssues; const actualIssues = validateAndFilter(services, languageNode, options); compareValidationIssues(actualIssues, expectedIssues); @@ -78,7 +122,11 @@ export function expectValidationIssues(services: TypirServices(services: TypirServices, languageNode: LanguageType, expectedStrictIssues: string[]): void; +export function expectValidationIssuesStrict( + services: TypirServices, + languageNode: LanguageType, + expectedStrictIssues: string[], +): void; /** * Tests, whether exactly the specified issues are found during the validation of the given language node, * i.e. neither more nor less validation issues. @@ -87,9 +135,21 @@ export function expectValidationIssuesStrict(services: TypirServic * @param options These options are used to filter all occurred issues before the expectations are checked * @param expectedStrictIssues the expected issues to occur */ -export function expectValidationIssuesStrict(services: TypirServices, languageNode: LanguageType, options: ExpectedValidationIssuesOptions, expectedStrictIssues: string[]): void; -export function expectValidationIssuesStrict(services: TypirServices, languageNode: LanguageType, optionsOrIssues: ExpectedValidationIssuesOptions | string[], issues?: string[]): void { - const expectedStrictIssues = Array.isArray(optionsOrIssues) ? optionsOrIssues : issues ?? []; +export function expectValidationIssuesStrict( + services: TypirServices, + languageNode: LanguageType, + options: ExpectedValidationIssuesOptions, + expectedStrictIssues: string[], +): void; +export function expectValidationIssuesStrict( + services: TypirServices, + languageNode: LanguageType, + optionsOrIssues: ExpectedValidationIssuesOptions | string[], + issues?: string[], +): void { + const expectedStrictIssues = Array.isArray(optionsOrIssues) + ? optionsOrIssues + : (issues ?? []); const options = Array.isArray(optionsOrIssues) ? {} : optionsOrIssues; const actualIssues = validateAndFilter(services, languageNode, options); compareValidationIssuesStrict(actualIssues, expectedStrictIssues); @@ -102,7 +162,11 @@ export function expectValidationIssuesStrict(services: TypirServic * @param languageNode the language node to validate * @param forbiddenIssues the issues which are expected to NOT occur */ -export function expectValidationIssuesAbsent(services: TypirServices, languageNode: LanguageType, forbiddenIssues: string[]): void; +export function expectValidationIssuesAbsent( + services: TypirServices, + languageNode: LanguageType, + forbiddenIssues: string[], +): void; /** * Tests, whether the specified issues are NOT found during the validation of the given language node, * other validation issues than the specified ones might occur. @@ -111,9 +175,21 @@ export function expectValidationIssuesAbsent(services: TypirServic * @param options These options are used to filter all occurred issues before the expectations are checked * @param forbiddenIssues the issues which are expected to NOT occur */ -export function expectValidationIssuesAbsent(services: TypirServices, languageNode: LanguageType, options: ExpectedValidationIssuesOptions, forbiddenIssues: string[]): void; -export function expectValidationIssuesAbsent(services: TypirServices, languageNode: LanguageType, optionsOrIssues: ExpectedValidationIssuesOptions | string[], issues?: string[]): void { - const expectedForbiddenIssues = Array.isArray(optionsOrIssues) ? optionsOrIssues : issues ?? []; +export function expectValidationIssuesAbsent( + services: TypirServices, + languageNode: LanguageType, + options: ExpectedValidationIssuesOptions, + forbiddenIssues: string[], +): void; +export function expectValidationIssuesAbsent( + services: TypirServices, + languageNode: LanguageType, + optionsOrIssues: ExpectedValidationIssuesOptions | string[], + issues?: string[], +): void { + const expectedForbiddenIssues = Array.isArray(optionsOrIssues) + ? optionsOrIssues + : (issues ?? []); const options = Array.isArray(optionsOrIssues) ? {} : optionsOrIssues; const actualIssues = validateAndFilter(services, languageNode, options); compareValidationIssuesAbsent(actualIssues, expectedForbiddenIssues); @@ -125,9 +201,17 @@ export function expectValidationIssuesAbsent(services: TypirServic * @param languageNode the language node to validate * @param options These options are used to filter all occurred issues before the expectations are checked */ -export function expectValidationIssuesNone(services: TypirServices, languageNode: LanguageType, options?: ExpectedValidationIssuesOptions): void { +export function expectValidationIssuesNone( + services: TypirServices, + languageNode: LanguageType, + options?: ExpectedValidationIssuesOptions, +): void { const optionsToUse = options ?? {}; - const actualIssues = validateAndFilter(services, languageNode, optionsToUse); + const actualIssues = validateAndFilter( + services, + languageNode, + optionsToUse, + ); compareValidationIssuesNone(actualIssues); } @@ -137,33 +221,58 @@ export interface ExpectedValidationIssuesOptions { // more properties for filtering might be added in the future } -function validateAndFilter(services: TypirServices, languageNode: LanguageType, options: ExpectedValidationIssuesOptions): string[] { +function validateAndFilter( + services: TypirServices, + languageNode: LanguageType, + options: ExpectedValidationIssuesOptions, +): string[] { return services.validation.Collector.validate(languageNode) - .filter(v => options.severity ? v.severity === options.severity : true) - .map(v => services.Printer.printTypirProblem(v)); + .filter((v) => + options.severity ? v.severity === options.severity : true, + ) + .map((v) => services.Printer.printTypirProblem(v)); } -export function compareValidationIssues(actualIssues: string[], expectedIssues: string[]): void { +export function compareValidationIssues( + actualIssues: string[], + expectedIssues: string[], +): void { compareValidationIssuesLogic(actualIssues, expectedIssues); } -export function compareValidationIssuesStrict(actualIssues: string[], expectedStrictIssues: string[]): void { - compareValidationIssuesLogic(actualIssues, expectedStrictIssues, { strict: true }); +export function compareValidationIssuesStrict( + actualIssues: string[], + expectedStrictIssues: string[], +): void { + compareValidationIssuesLogic(actualIssues, expectedStrictIssues, { + strict: true, + }); } -export function compareValidationIssuesAbsent(actualIssues: string[], forbiddenIssues: string[]): void { +export function compareValidationIssuesAbsent( + actualIssues: string[], + forbiddenIssues: string[], +): void { compareValidationIssuesLogicAbsent(actualIssues, forbiddenIssues); } export function compareValidationIssuesNone(actualIssues: string[]): void { compareValidationIssuesLogic(actualIssues, [], { strict: true }); } -function compareValidationIssuesLogic(actualIssues: string[], expectedErrors: string[], options?: { strict?: boolean }): void { +function compareValidationIssuesLogic( + actualIssues: string[], + expectedErrors: string[], + options?: { strict?: boolean }, +): void { // compare actual and expected issues let indexExpected = 0; while (indexExpected < expectedErrors.length) { let indexActual = 0; let found = false; while (indexActual < actualIssues.length) { - if (actualIssues[indexActual].includes(expectedErrors[indexExpected])) { + if ( + actualIssues[indexActual].includes( + expectedErrors[indexExpected], + ) + ) { found = true; // remove found matches => at the end, the not matching issues remain to be reported actualIssues.splice(indexActual, 1); @@ -179,13 +288,17 @@ function compareValidationIssuesLogic(actualIssues: string[], expectedErrors: st } } // report the result - const msgExpected = expectedErrors.join('\n').trim(); - const msgActual = actualIssues.join('\n').trim(); + const msgExpected = expectedErrors.join("\n").trim(); + const msgActual = actualIssues.join("\n").trim(); if (msgExpected.length >= 1 && msgActual.length >= 1) { if (options?.strict) { - expect.fail(`Didn't find expected issues:\n${msgExpected}\nBut found some more issues:\n${msgActual}`); + expect.fail( + `Didn't find expected issues:\n${msgExpected}\nBut found some more issues:\n${msgActual}`, + ); } else { - expect.fail(`Didn't find expected issues:\n${msgExpected}\nThese other issues are ignored:\n${msgActual}`); + expect.fail( + `Didn't find expected issues:\n${msgExpected}\nThese other issues are ignored:\n${msgActual}`, + ); // printing the ignored issues help to identify typos, ... in the specified issues } } else if (msgExpected.length >= 1) { @@ -200,14 +313,21 @@ function compareValidationIssuesLogic(actualIssues: string[], expectedErrors: st // everything is fine } } -function compareValidationIssuesLogicAbsent(actualIssues: string[], forbiddenErrors: string[]): void { +function compareValidationIssuesLogicAbsent( + actualIssues: string[], + forbiddenErrors: string[], +): void { // compare actual and expected issues let indexExpected = 0; while (indexExpected < forbiddenErrors.length) { let indexActual = 0; let found = false; while (indexActual < actualIssues.length) { - if (actualIssues[indexActual].includes(forbiddenErrors[indexExpected])) { + if ( + actualIssues[indexActual].includes( + forbiddenErrors[indexExpected], + ) + ) { found = true; break; } @@ -223,10 +343,12 @@ function compareValidationIssuesLogicAbsent(actualIssues: string[], forbiddenErr } } // report the result - const msgForbidden = forbiddenErrors.join('\n').trim(); - const msgActual = actualIssues.join('\n').trim(); + const msgForbidden = forbiddenErrors.join("\n").trim(); + const msgActual = actualIssues.join("\n").trim(); if (msgForbidden.length >= 1 && msgActual.length >= 1) { - expect.fail(`Found these forbidden issues:\n${msgForbidden}\nThese other issues are ignored:\n${msgActual}`); + expect.fail( + `Found these forbidden issues:\n${msgForbidden}\nThese other issues are ignored:\n${msgActual}`, + ); // printing the ignored issues help to identify typos, ... in the specified forbidden issues } else if (msgForbidden.length >= 1) { expect.fail(`Found these forbidden issues:\n${msgForbidden}`); @@ -237,7 +359,6 @@ function compareValidationIssuesLogicAbsent(actualIssues: string[], forbiddenErr } } - /** * Creates TypirServices dedicated for testing purposes, * with the default module containing the default implements for Typir, which might be exchanged by the given optional customized module. @@ -245,14 +366,18 @@ function compareValidationIssuesLogicAbsent(actualIssues: string[], forbiddenErr * @returns a Typir instance, i.e. the TypirServices with implementations */ export function createTypirServicesForTesting( - customizationForTesting: Module, PartialTypirServices> = {}, + customizationForTesting: Module< + TypirServices, + PartialTypirServices + > = {}, ): TypirServices { return createTypirServices( - createDefaultTypirServicesModule(), // all default core implementations - { // override some default implementations: - Printer: () => new TestProblemPrinter(), // use the dedicated printer for TestLanguageNode's - Language: () => new TestLanguageService(), // provide language keys for the TestLanguageNode's: they are just the names of the classes (without extends so far) + createDefaultTypirServicesModule(), // all default core implementations + { + // override some default implementations: + Printer: () => new TestProblemPrinter(), // use the dedicated printer for TestLanguageNode's + Language: () => new TestLanguageService(), // provide language keys for the TestLanguageNode's: they are just the names of the classes (without extends so far) }, - customizationForTesting, // specific customizations for the current test case + customizationForTesting, // specific customizations for the current test case ); } diff --git a/packages/typir/src/utils/utils-definitions.ts b/packages/typir/src/utils/utils-definitions.ts index 82414e5a..029df746 100644 --- a/packages/typir/src/utils/utils-definitions.ts +++ b/packages/typir/src/utils/utils-definitions.ts @@ -4,14 +4,20 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { isType, Type } from '../graph/type-node.js'; -import { TypeInitializer } from '../initialization/type-initializer.js'; -import { InferenceRuleNotApplicable, TypeInferenceRule, TypeInferenceRuleOptions } from '../services/inference.js'; -import { ValidationProblemAcceptor, ValidationRule, ValidationRuleOptions } from '../services/validation.js'; -import { TypirServices } from '../typir.js'; -import { toArray } from './utils.js'; +import { isType, Type } from "../graph/type-node.js"; +import { TypeInitializer } from "../initialization/type-initializer.js"; +import { + InferenceRuleNotApplicable, + TypeInferenceRule, + TypeInferenceRuleOptions, +} from "../services/inference.js"; +import { + ValidationProblemAcceptor, + ValidationRule, + ValidationRuleOptions, +} from "../services/validation.js"; +import { TypirServices } from "../typir.js"; +import { toArray } from "./utils.js"; /** * Common interface of all problems/errors/messages which should be shown to users of DSLs which are type-checked with Typir. @@ -20,40 +26,61 @@ import { toArray } from './utils.js'; export interface TypirProblem { readonly $problem: string; } -export function isSpecificTypirProblem(problem: unknown, $problem: string): problem is TypirProblem { - return typeof problem === 'object' && problem !== null && ((problem as TypirProblem).$problem === $problem); +export function isSpecificTypirProblem( + problem: unknown, + $problem: string, +): problem is TypirProblem { + return ( + typeof problem === "object" && + problem !== null && + (problem as TypirProblem).$problem === $problem + ); } export type Types = Type | Type[]; export type Names = string | string[]; -export type TypeInitializers = TypeInitializer | Array>; +export type TypeInitializers = + | TypeInitializer + | Array>; export type NameTypePair = { name: string; type: Type; -} +}; export function isNameTypePair(type: unknown): type is NameTypePair { - return typeof type === 'object' && type !== null && typeof (type as NameTypePair).name === 'string' && isType((type as NameTypePair).type); + return ( + typeof type === "object" && + type !== null && + typeof (type as NameTypePair).name === "string" && + isType((type as NameTypePair).type) + ); } - - // // Utilities for validations // /** A pair of a rule for type inference with its additional options. */ -export interface ValidationRuleWithOptions { +export interface ValidationRuleWithOptions< + LanguageType, + T extends LanguageType = LanguageType, +> { rule: ValidationRule; options: Partial; } -export function bindValidateCurrentTypeRule( - rule: InferCurrentTypeRule, type: TypeType +export function bindValidateCurrentTypeRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +>( + rule: InferCurrentTypeRule, + type: TypeType, ): ValidationRuleWithOptions | undefined { // check the given rule checkRule(rule); // fail early - if (toArray(rule.validation).length <= 0) { // there are no checks => don't create a validation rule! + if (toArray(rule.validation).length <= 0) { + // there are no checks => don't create a validation rule! return undefined; } // create a single validation rule with options @@ -61,10 +88,16 @@ export function bindValidateCurrentTypeRule { // when this validation rule is executed, it is already ensured, that the (non-undefined) language key of rule and language node fit! - if (rule.filter !== undefined && rule.filter(languageNode) === false) { + if ( + rule.filter !== undefined && + rule.filter(languageNode) === false + ) { return; // if specified, the filter needs to accept the current language node } - if (rule.matching !== undefined && rule.matching(languageNode, type) === false) { + if ( + rule.matching !== undefined && + rule.matching(languageNode, type) === false + ) { return; // if specified, the current language node needs to match the condition of the inference rule } // since the current language node fits to this inference rule, validate it according @@ -75,11 +108,10 @@ export function bindValidateCurrentTypeRule; + registration: "MYSELF" | Partial; } - // // Utilities for type inference // /** A pair of a rule for type inference with its additional options. */ -export interface InferenceRuleWithOptions { +export interface InferenceRuleWithOptions< + LanguageType, + T extends LanguageType = LanguageType, +> { rule: TypeInferenceRule; options: Partial; } -export function optionsBoundToType | Partial>(options: T, type: Type | undefined): T { +export function optionsBoundToType< + T extends + | Partial + | Partial, +>(options: T, type: Type | undefined): T { return { ...options, boundToType: type, @@ -111,21 +149,28 @@ export function optionsBoundToType | } export function ruleWithOptionsBoundToType< - LanguageType, T extends LanguageType = LanguageType ->(rule: InferenceRuleWithOptions, type: Type | undefined): InferenceRuleWithOptions { + LanguageType, + T extends LanguageType = LanguageType, +>( + rule: InferenceRuleWithOptions, + type: Type | undefined, +): InferenceRuleWithOptions { return { rule: rule.rule, options: optionsBoundToType(rule.options, type), }; } - /** * An inference rule which is dedicated for inferrring a certain type. * This utility type is often used for inference rules which are annotated to the declaration of a type. * At least one of the properties needs to be specified. */ -export interface InferCurrentTypeRule { +export interface InferCurrentTypeRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +> { languageKey?: string | string[]; filter?: (languageNode: LanguageType) => languageNode is T; matching?: (languageNode: T, typeToInfer: TypeType) => boolean; @@ -134,21 +179,45 @@ export interface InferCurrentTypeRule | Array>; + validation?: + | InferCurrentTypeValidationRule + | Array>; } -export type InferCurrentTypeValidationRule = - (languageNode: T, inferredType: TypeType, accept: ValidationProblemAcceptor, typir: TypirServices) => void; +export type InferCurrentTypeValidationRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +> = ( + languageNode: T, + inferredType: TypeType, + accept: ValidationProblemAcceptor, + typir: TypirServices, +) => void; - -function checkRule(rule: InferCurrentTypeRule): void { - if (rule.languageKey === undefined && rule.filter === undefined && rule.matching === undefined) { - throw new Error('This inference rule has none of the properties "languageKey", "filter" and "matching" at all and therefore cannot infer any type!'); +function checkRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +>(rule: InferCurrentTypeRule): void { + if ( + rule.languageKey === undefined && + rule.filter === undefined && + rule.matching === undefined + ) { + throw new Error( + 'This inference rule has none of the properties "languageKey", "filter" and "matching" at all and therefore cannot infer any type!', + ); } } -export function bindInferCurrentTypeRule( - rule: InferCurrentTypeRule, type: TypeType +export function bindInferCurrentTypeRule< + TypeType extends Type, + LanguageType, + T extends LanguageType = LanguageType, +>( + rule: InferCurrentTypeRule, + type: TypeType, ): InferenceRuleWithOptions { checkRule(rule); // fail early return { @@ -180,27 +249,41 @@ export function bindInferCurrentTypeRule( - rules: InferCurrentTypeRule | Array> | undefined, type: TypeType, services: TypirServices +export function registerInferCurrentTypeRules< + TypeType extends Type, + LanguageType, +>( + rules: + | InferCurrentTypeRule + | Array> + | undefined, + type: TypeType, + services: TypirServices, ): void { for (const ruleSingle of toArray(rules)) { // inference - const {rule: ruleInfer, options: optionsInfer} = bindInferCurrentTypeRule(ruleSingle, type); + const { rule: ruleInfer, options: optionsInfer } = + bindInferCurrentTypeRule(ruleSingle, type); services.Inference.addInferenceRule(ruleInfer, optionsInfer); // validation const validate = bindValidateCurrentTypeRule(ruleSingle, type); if (validate) { - services.validation.Collector.addValidationRule(validate.rule, validate.options); + services.validation.Collector.addValidationRule( + validate.rule, + validate.options, + ); } } // In theory, there is a small performance optimization possible: diff --git a/packages/typir/src/utils/utils-type-comparison.ts b/packages/typir/src/utils/utils-type-comparison.ts index 0d99fb2e..2300b7d1 100644 --- a/packages/typir/src/utils/utils-type-comparison.ts +++ b/packages/typir/src/utils/utils-type-comparison.ts @@ -4,64 +4,79 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from 'langium'; -import { isType, Type } from '../graph/type-node.js'; -import { Kind } from '../kinds/kind.js'; -import { InferenceProblem } from '../services/inference.js'; -import { TypirServices } from '../typir.js'; -import { assertTrue } from '../utils/utils.js'; -import { isNameTypePair, isSpecificTypirProblem, NameTypePair, TypirProblem } from './utils-definitions.js'; +import { assertUnreachable } from "langium"; +import { isType, Type } from "../graph/type-node.js"; +import { Kind } from "../kinds/kind.js"; +import { InferenceProblem } from "../services/inference.js"; +import { TypirServices } from "../typir.js"; +import { assertTrue } from "../utils/utils.js"; +import { + isNameTypePair, + isSpecificTypirProblem, + NameTypePair, + TypirProblem, +} from "./utils-definitions.js"; export type TypeCheckStrategy = - 'EQUAL_TYPE' | // the most strict checking - 'ASSIGNABLE_TYPE' | // SUB_TYPE or implicit conversion - 'SUB_TYPE'; // more relaxed checking + | "EQUAL_TYPE" // the most strict checking + | "ASSIGNABLE_TYPE" // SUB_TYPE or implicit conversion + | "SUB_TYPE"; // more relaxed checking -export function createTypeCheckStrategy(strategy: TypeCheckStrategy, typir: TypirServices): (t1: Type, t2: Type) => TypirProblem | undefined { +export function createTypeCheckStrategy( + strategy: TypeCheckStrategy, + typir: TypirServices, +): (t1: Type, t2: Type) => TypirProblem | undefined { switch (strategy) { - case 'ASSIGNABLE_TYPE': + case "ASSIGNABLE_TYPE": return typir.Assignability.getAssignabilityProblem // t1 === source, t2 === target .bind(typir.Assignability); - case 'EQUAL_TYPE': + case "EQUAL_TYPE": return typir.Equality.getTypeEqualityProblem // (unordered, order does not matter) .bind(typir.Equality); - case 'SUB_TYPE': + case "SUB_TYPE": return typir.Subtype.getSubTypeProblem // t1 === sub, t2 === super .bind(typir.Subtype); - // .bind(...) is required to have the correct value for 'this' inside the referenced function/method! - // see https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback + // .bind(...) is required to have the correct value for 'this' inside the referenced function/method! + // see https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback default: assertUnreachable(strategy); } } export interface ValueConflict extends TypirProblem { - readonly $problem: 'ValueConflict'; + readonly $problem: "ValueConflict"; // 'undefined' means value is missing, 'string' is the string representation of the value firstValue: string | undefined; secondValue: string | undefined; location: string; } -export const ValueConflict = 'ValueConflict'; +export const ValueConflict = "ValueConflict"; export function isValueConflict(problem: unknown): problem is ValueConflict { return isSpecificTypirProblem(problem, ValueConflict); } -export function checkValueForConflict(first: T, second: T, location: string, - relationToCheck: (e: T, a: T) => boolean = (e, a) => e === a): ValueConflict[] { +export function checkValueForConflict( + first: T, + second: T, + location: string, + relationToCheck: (e: T, a: T) => boolean = (e, a) => e === a, +): ValueConflict[] { const conflicts: ValueConflict[] = []; if (relationToCheck(first, second) === false) { conflicts.push({ $problem: ValueConflict, firstValue: `${first}`, secondValue: `${second}`, - location + location, }); } return conflicts; } -export function createKindConflict(first: Type | Kind, second: Type | Kind): ValueConflict { +export function createKindConflict( + first: Type | Kind, + second: Type | Kind, +): ValueConflict { if (isType(first)) { first = first.kind; } @@ -72,12 +87,12 @@ export function createKindConflict(first: Type | Kind, second: Type | Kind): Val $problem: ValueConflict, firstValue: first.$name, secondValue: second.$name, - location: 'kind', + location: "kind", }; } export interface IndexedTypeConflict extends TypirProblem { - $problem: 'IndexedTypeConflict'; + $problem: "IndexedTypeConflict"; // 'undefined' means type or information is missing, 'string' is for data which are no Types expected: Type | undefined; // first, left actual: Type | undefined; // second, right @@ -86,15 +101,25 @@ export interface IndexedTypeConflict extends TypirProblem { propertyName?: string; subProblems: TypirProblem[]; } -export const IndexedTypeConflict = 'IndexedTypeConflict'; -export function isIndexedTypeConflict(problem: unknown): problem is IndexedTypeConflict { +export const IndexedTypeConflict = "IndexedTypeConflict"; +export function isIndexedTypeConflict( + problem: unknown, +): problem is IndexedTypeConflict { return isSpecificTypirProblem(problem, IndexedTypeConflict); } -export type TypeToCheck = Type | NameTypePair | undefined | Array>; +export type TypeToCheck = + | Type + | NameTypePair + | undefined + | Array>; -export function checkTypes(left: TypeToCheck, right: TypeToCheck, - relationToCheck: (l: Type, r: Type) => (TypirProblem | undefined), checkNamesOfNameTypePairs: boolean): IndexedTypeConflict[] { +export function checkTypes( + left: TypeToCheck, + right: TypeToCheck, + relationToCheck: (l: Type, r: Type) => TypirProblem | undefined, + checkNamesOfNameTypePairs: boolean, +): IndexedTypeConflict[] { const conflicts: IndexedTypeConflict[] = []; // check first common indices const leftInferenceProblems = Array.isArray(left); @@ -139,7 +164,9 @@ export function checkTypes(left: TypeToCheck, right: const subProblems: TypirProblem[] = []; if (isLeftPair && isRightPair && checkNamesOfNameTypePairs) { - subProblems.push(...checkValueForConflict(left.name, right.name, 'name')); + subProblems.push( + ...checkValueForConflict(left.name, right.name, "name"), + ); } const relationCheckResult = relationToCheck(leftType, rightType); if (relationCheckResult !== undefined) { @@ -151,7 +178,11 @@ export function checkTypes(left: TypeToCheck, right: $problem: IndexedTypeConflict, expected: leftType, actual: rightType, - propertyName: isLeftPair ? left.name : (isRightPair ? right.name : undefined), + propertyName: isLeftPair + ? left.name + : isRightPair + ? right.name + : undefined, subProblems: subProblems, }); } else { @@ -163,13 +194,26 @@ export function checkTypes(left: TypeToCheck, right: return conflicts; } -export function checkTypeArrays(leftTypes: Array>, rightTypes: Array>, - relationToCheck: (l: Type, r: Type, index: number) => (TypirProblem | undefined), checkNamesOfNameTypePairs: boolean): IndexedTypeConflict[] { +export function checkTypeArrays( + leftTypes: Array>, + rightTypes: Array>, + relationToCheck: ( + l: Type, + r: Type, + index: number, + ) => TypirProblem | undefined, + checkNamesOfNameTypePairs: boolean, +): IndexedTypeConflict[] { const conflicts: IndexedTypeConflict[] = []; // check first common indices for (let i = 0; i < Math.min(leftTypes.length, rightTypes.length); i++) { - const currentProblems = checkTypes(leftTypes[i], rightTypes[i], (l, r) => relationToCheck(l, r, i), checkNamesOfNameTypePairs); - currentProblems.forEach(p => p.propertyIndex = i); // add the index + const currentProblems = checkTypes( + leftTypes[i], + rightTypes[i], + (l, r) => relationToCheck(l, r, i), + checkNamesOfNameTypePairs, + ); + currentProblems.forEach((p) => (p.propertyIndex = i)); // add the index conflicts.push(...currentProblems); } // missing in the left @@ -207,7 +251,10 @@ export function checkTypeArrays(leftTypes: Array, targetFields: Map, relationToCheck: (s: Type, t: Type) => (TypirProblem | undefined)): IndexedTypeConflict[] { +export function checkNameTypesMap( + sourceFields: Map, + targetFields: Map, + relationToCheck: (s: Type, t: Type) => TypirProblem | undefined, +): IndexedTypeConflict[] { const targetCopy = new Map(targetFields); const conflicts: IndexedTypeConflict[] = []; for (const entry of sourceFields.entries()) { @@ -268,7 +321,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: undefined, actual: targetType, propertyName: name, - subProblems: [] + subProblems: [], }); } else if (sourceType !== undefined && targetType === undefined) { // only the source type exists @@ -277,11 +330,14 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: sourceType, actual: undefined, propertyName: name, - subProblems: [] + subProblems: [], }); } else if (sourceType !== undefined && targetType !== undefined) { // both types exist => check them - const relationCheckResult = relationToCheck(sourceType, targetType); + const relationCheckResult = relationToCheck( + sourceType, + targetType, + ); if (relationCheckResult !== undefined) { // different types conflicts.push({ @@ -289,13 +345,13 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: sourceType, actual: targetType, propertyName: name, - subProblems: [relationCheckResult] + subProblems: [relationCheckResult], }); } else { // same type } } else { - throw new Error('impossible case'); + throw new Error("impossible case"); } } else { // field is missing in target @@ -307,7 +363,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: sourceType, actual: undefined, propertyName: name, - subProblems: [] + subProblems: [], }); } } @@ -322,7 +378,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: undefined, actual, propertyName: index, - subProblems: [] + subProblems: [], }); } } @@ -337,7 +393,7 @@ export class MapListConverter { return Array.from(values) .map(([fieldName, fieldType]) => ({ fieldName, fieldType })) .sort((e1, e2) => e1.fieldName.localeCompare(e2.fieldName)) - .map(e => { + .map((e) => { this.names.push(e.fieldName); return e.fieldType; }); diff --git a/packages/typir/src/utils/utils.ts b/packages/typir/src/utils/utils.ts index b48aa8fa..a56a8f04 100644 --- a/packages/typir/src/utils/utils.ts +++ b/packages/typir/src/utils/utils.ts @@ -4,16 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from '../graph/type-node.js'; -import { Kind } from '../kinds/kind.js'; +import { Type } from "../graph/type-node.js"; +import { Kind } from "../kinds/kind.js"; -export function assertTrue(condition: boolean, msg?: string): asserts condition { +export function assertTrue( + condition: boolean, + msg?: string, +): asserts condition { if (!condition) { throw new Error(msg); } } -export function toArray(value: undefined | T | T[], options?: { newArray: boolean }): T[] { +export function toArray( + value: undefined | T | T[], + options?: { newArray: boolean }, +): T[] { if (value === undefined) { return []; } @@ -26,7 +32,10 @@ export function toArray(value: undefined | T | T[], options?: { newArray: boo } return [value]; } -export function toArrayWithValue(value: T, array?: undefined | T | T[]): T[] { +export function toArrayWithValue( + value: T, + array?: undefined | T | T[], +): T[] { if (array === undefined) { return [value]; } @@ -37,7 +46,10 @@ export function toArrayWithValue(value: T, array?: undefined | T | T[]): T[] return [array, value]; } -export function removeFromArray(value: T | undefined, array: T[] | undefined): boolean { +export function removeFromArray( + value: T | undefined, + array: T[] | undefined, +): boolean { if (value === undefined || array === undefined) { return false; } @@ -51,10 +63,14 @@ export function removeFromArray(value: T | undefined, array: T[] | undefined) } export function assertUnreachable(_: never): never { - throw new Error('Error! The input value was not handled.'); + throw new Error("Error! The input value was not handled."); } -export function assertKind(kind: unknown, check: (kind: unknown) => kind is T, msg?: string): asserts kind is T { +export function assertKind( + kind: unknown, + check: (kind: unknown) => kind is T, + msg?: string, +): asserts kind is T { if (check(kind)) { // this is the expected case } else { @@ -62,7 +78,11 @@ export function assertKind(kind: unknown, check: (kind: unknown) } } -export function assertTypirType(type: unknown, check: (type: unknown) => type is T, msg?: string): asserts type is T { +export function assertTypirType( + type: unknown, + check: (type: unknown) => type is T, + msg?: string, +): asserts type is T { if (check(type)) { // this is the expected case } else { diff --git a/packages/typir/test/api-example.test.ts b/packages/typir/test/api-example.test.ts index ffca37c0..cff0d33e 100644 --- a/packages/typir/test/api-example.test.ts +++ b/packages/typir/test/api-example.test.ts @@ -6,38 +6,65 @@ /* eslint-disable @typescript-eslint/parameter-properties */ -import { describe, expect, test } from 'vitest'; -import { InferenceRuleNotApplicable } from '../src/services/inference.js'; -import { InferOperatorWithMultipleOperands } from '../src/services/operator.js'; -import { createTypirServices } from '../src/typir.js'; +import { describe, expect, test } from "vitest"; +import { InferenceRuleNotApplicable } from "../src/services/inference.js"; +import { InferOperatorWithMultipleOperands } from "../src/services/operator.js"; +import { createTypirServices } from "../src/typir.js"; -describe('Tiny Typir', () => { - - test('Set-up and test some expressions', async () => { +describe("Tiny Typir", () => { + test("Set-up and test some expressions", async () => { const typir = createTypirServices(); // set-up the type system, specifies the root type of all language nodes // primitive types - const numberType = typir.factory.Primitives.create({ primitiveName: 'number' }).inferenceRule({ filter: node => node instanceof NumberLiteral }).finish(); - const stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); + const numberType = typir.factory.Primitives.create({ + primitiveName: "number", + }) + .inferenceRule({ filter: (node) => node instanceof NumberLiteral }) + .finish(); + const stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ filter: (node) => node instanceof StringLiteral }) + .finish(); // operators - const inferenceRule: InferOperatorWithMultipleOperands = { - filter: node => node instanceof BinaryExpression, + const inferenceRule: InferOperatorWithMultipleOperands< + AstElement, + BinaryExpression + > = { + filter: (node) => node instanceof BinaryExpression, matching: (node, operatorName) => node.operator === operatorName, - operands: node => [node.left, node.right], + operands: (node) => [node.left, node.right], validateArgumentsOfCalls: true, // explicitly request to check, that the types of the arguments in operator calls fit to the parameters }; - typir.factory.Operators.createBinary({ name: '+', signatures: [ // operator overloading - { left: numberType, right: numberType, return: numberType }, // 2 + 3 - { left: stringType, right: stringType, return: stringType }, // "2" + "3" - ] }).inferenceRule(inferenceRule).finish(); - typir.factory.Operators.createBinary({ name: '-', signatures: [{ left: numberType, right: numberType, return: numberType }] }).inferenceRule(inferenceRule).finish(); // 2 - 3 + typir.factory.Operators.createBinary({ + name: "+", + signatures: [ + // operator overloading + { left: numberType, right: numberType, return: numberType }, // 2 + 3 + { left: stringType, right: stringType, return: stringType }, // "2" + "3" + ], + }) + .inferenceRule(inferenceRule) + .finish(); + typir.factory.Operators.createBinary({ + name: "-", + signatures: [ + { left: numberType, right: numberType, return: numberType }, + ], + }) + .inferenceRule(inferenceRule) + .finish(); // 2 - 3 // numbers are implicitly convertable to strings - typir.Conversion.markAsConvertible(numberType, stringType, 'IMPLICIT_EXPLICIT'); + typir.Conversion.markAsConvertible( + numberType, + stringType, + "IMPLICIT_EXPLICIT", + ); // specify, how Typir can detect the type of a variable - typir.Inference.addInferenceRule(node => { + typir.Inference.addInferenceRule((node) => { if (node instanceof Variable) { return node.initialValue; // the type of the variable is the type of its initial value } @@ -47,38 +74,70 @@ describe('Tiny Typir', () => { // register a type-related validation typir.validation.Collector.addValidationRule((node, accept) => { if (node instanceof AssignmentStatement) { - typir.validation.Constraints.ensureNodeIsAssignable(node.right, node.left, accept, (actual, expected) => ({ message: - `The type '${actual.name}' is not assignable to the type '${expected.name}'.` })); + typir.validation.Constraints.ensureNodeIsAssignable( + node.right, + node.left, + accept, + (actual, expected) => ({ + message: `The type '${actual.name}' is not assignable to the type '${expected.name}'.`, + }), + ); } }); // 2 + 3 => OK - const example1 = new BinaryExpression(new NumberLiteral(2), '+', new NumberLiteral(3)); + const example1 = new BinaryExpression( + new NumberLiteral(2), + "+", + new NumberLiteral(3), + ); expect(typir.validation.Collector.validate(example1)).toHaveLength(0); // 2 + "3" => OK - const example2 = new BinaryExpression(new NumberLiteral(2), '+', new StringLiteral('3')); + const example2 = new BinaryExpression( + new NumberLiteral(2), + "+", + new StringLiteral("3"), + ); expect(typir.validation.Collector.validate(example2)).toHaveLength(0); // 2 - "3" => wrong - const example3 = new BinaryExpression(new NumberLiteral(2), '-', new StringLiteral('3')); + const example3 = new BinaryExpression( + new NumberLiteral(2), + "-", + new StringLiteral("3"), + ); const errors1 = typir.validation.Collector.validate(example3); const errorStack = typir.Printer.printTypirProblem(errors1[0]); // the problem comes with "sub-problems" to describe the reasons in more detail - expect(errorStack).includes("The parameter 'right' at index 1 got a value with a wrong type."); - expect(errorStack).includes("For property 'right', the types 'string' and 'number' do not match."); + expect(errorStack).includes( + "The parameter 'right' at index 1 got a value with a wrong type.", + ); + expect(errorStack).includes( + "For property 'right', the types 'string' and 'number' do not match.", + ); // 123 is assignable to a string variable - const varString = new Variable('v1', new StringLiteral('Hello')); - const assignNumberToString = new AssignmentStatement(varString, new NumberLiteral(123)); - expect(typir.validation.Collector.validate(assignNumberToString)).toHaveLength(0); + const varString = new Variable("v1", new StringLiteral("Hello")); + const assignNumberToString = new AssignmentStatement( + varString, + new NumberLiteral(123), + ); + expect( + typir.validation.Collector.validate(assignNumberToString), + ).toHaveLength(0); // "123" is not assignable to a number variable - const varNumber = new Variable('v2', new NumberLiteral(456)); - const assignStringToNumber = new AssignmentStatement(varNumber, new StringLiteral('123')); - const errors2 = typir.validation.Collector.validate(assignStringToNumber); - expect(errors2[0].message).toBe("The type 'string' is not assignable to the type 'number'."); + const varNumber = new Variable("v2", new NumberLiteral(456)); + const assignStringToNumber = new AssignmentStatement( + varNumber, + new StringLiteral("123"), + ); + const errors2 = + typir.validation.Collector.validate(assignStringToNumber); + expect(errors2[0].message).toBe( + "The type 'string' is not assignable to the type 'number'.", + ); }); - }); abstract class AstElement { @@ -86,14 +145,14 @@ abstract class AstElement { } class NumberLiteral extends AstElement { - constructor( - public value: number, - ) { super(); } + constructor(public value: number) { + super(); + } } class StringLiteral extends AstElement { - constructor( - public value: string, - ) { super(); } + constructor(public value: string) { + super(); + } } class BinaryExpression extends AstElement { @@ -101,19 +160,25 @@ class BinaryExpression extends AstElement { public left: AstElement, public operator: string, public right: AstElement, - ) { super(); } + ) { + super(); + } } class Variable extends AstElement { constructor( public name: string, public initialValue: AstElement, - ) { super(); } + ) { + super(); + } } class AssignmentStatement extends AstElement { constructor( public left: Variable, public right: AstElement, - ) { super(); } + ) { + super(); + } } diff --git a/packages/typir/test/kinds/class/class.test.ts b/packages/typir/test/kinds/class/class.test.ts index 686ca00e..29d72fbd 100644 --- a/packages/typir/test/kinds/class/class.test.ts +++ b/packages/typir/test/kinds/class/class.test.ts @@ -4,77 +4,139 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from 'vitest'; -import { isClassType } from '../../../src/kinds/class/class-type.js'; -import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; -import { BooleanLiteral, ClassConstructorCall, ClassFieldAccess, IntegerLiteral, Variable } from '../../../src/test/predefined-language-nodes.js'; -import { createTypirServicesForTesting, expectToBeType, expectTypirTypes, expectValidationIssuesStrict } from '../../../src/utils/test-utils.js'; -import { assertTypirType } from '../../../src/utils/utils.js'; +import { describe, test } from "vitest"; +import { isClassType } from "../../../src/kinds/class/class-type.js"; +import { isPrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +import { + BooleanLiteral, + ClassConstructorCall, + ClassFieldAccess, + IntegerLiteral, + Variable, +} from "../../../src/test/predefined-language-nodes.js"; +import { + createTypirServicesForTesting, + expectToBeType, + expectTypirTypes, + expectValidationIssuesStrict, +} from "../../../src/utils/test-utils.js"; +import { assertTypirType } from "../../../src/utils/utils.js"; -describe('Tests some details for class types', () => { - - test('create primitive and get it by name', () => { +describe("Tests some details for class types", () => { + test("create primitive and get it by name", () => { const typir = createTypirServicesForTesting(); - const classType1 = typir.factory.Classes - .create({ className: 'MyClass1', fields: [], methods: [] }).finish() + const classType1 = typir.factory.Classes.create({ + className: "MyClass1", + fields: [], + methods: [], + }) + .finish() .getTypeFinal(); // since this class has no delayed dependencies, the new class type is directly available! - assertTypirType(classType1, isClassType, 'MyClass1'); - expectTypirTypes(typir, isClassType, 'MyClass1'); + assertTypirType(classType1, isClassType, "MyClass1"); + expectTypirTypes(typir, isClassType, "MyClass1"); }); - test('infer types of accessed fields of a class (and validate them)', () => { + test("infer types of accessed fields of a class (and validate them)", () => { const typir = createTypirServicesForTesting(); - const integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }) - .inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - const booleanType = typir.factory.Primitives.create({ primitiveName: 'boolean' }) - .inferenceRule({ filter: node => node instanceof BooleanLiteral }).finish(); + const integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + const booleanType = typir.factory.Primitives.create({ + primitiveName: "boolean", + }) + .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) + .finish(); const classType1 = typir.factory.Classes // a class with two fields with different primitive types - .create({ className: 'MyClass1', fields: [ - { name: 'fieldInteger', type: integerType }, - { name: 'fieldBoolean', type: booleanType }, - ], methods: [] }) + .create({ + className: "MyClass1", + fields: [ + { name: "fieldInteger", type: integerType }, + { name: "fieldBoolean", type: booleanType }, + ], + methods: [], + }) // infer the type for constructor calls .inferenceRuleForClassLiterals({ - filter: node => node instanceof ClassConstructorCall, - matching: node => node.className === 'MyClass1', - inputValuesForFields: _node => new Map(), + filter: (node) => node instanceof ClassConstructorCall, + matching: (node) => node.className === "MyClass1", + inputValuesForFields: (_node) => new Map(), // a useless validation just for testing - validation: (node, classType, accept, _typir) => accept({ languageNode: node, severity: 'error', message: `Called constructor for '${classType.getName()}'.` }), + validation: (node, classType, accept, _typir) => + accept({ + languageNode: node, + severity: "error", + message: `Called constructor for '${classType.getName()}'.`, + }), }) // infer the type when accessing fields .inferenceRuleForFieldAccess({ - filter: node => node instanceof ClassFieldAccess, + filter: (node) => node instanceof ClassFieldAccess, matching: (node, classType) => { - const variableType = typir.Inference.inferType(node.classVariable); + const variableType = typir.Inference.inferType( + node.classVariable, + ); return variableType === classType; }, - field: node => node.fieldName, + field: (node) => node.fieldName, // a useless validation just for testing validation: (node, classType, accept) => { - if (node.fieldName === 'fieldBoolean') { - accept({ languageNode: node, severity: 'error', message: `Validated access of 'fieldBoolean' of the variable '${node.classVariable.name}'.` }); + if (node.fieldName === "fieldBoolean") { + accept({ + languageNode: node, + severity: "error", + message: `Validated access of 'fieldBoolean' of the variable '${node.classVariable.name}'.`, + }); } }, }) - .finish().getTypeFinal(); - assertTypirType(classType1, isClassType, 'MyClass1'); - typir.Inference.addInferenceRule((node: Variable) => node.initialValue, { languageKey: Variable.name }); // infer the type of variables - + .finish() + .getTypeFinal(); + assertTypirType(classType1, isClassType, "MyClass1"); + typir.Inference.addInferenceRule( + (node: Variable) => node.initialValue, + { languageKey: Variable.name }, + ); // infer the type of variables // var1 := new MyClass1(); - const varClass = new Variable('var1', new ClassConstructorCall('MyClass1')); - expectValidationIssuesStrict(typir, varClass.initialValue, ["Called constructor for 'MyClass1'."]); + const varClass = new Variable( + "var1", + new ClassConstructorCall("MyClass1"), + ); + expectValidationIssuesStrict(typir, varClass.initialValue, [ + "Called constructor for 'MyClass1'.", + ]); // var2 := var1.fieldInteger; - const varFieldIntegerValue = new Variable('var2', new ClassFieldAccess(varClass, 'fieldInteger')); - expectToBeType(typir.Inference.inferType(varFieldIntegerValue), isPrimitiveType, type => type.getName() === 'integer'); - expectValidationIssuesStrict(typir, varFieldIntegerValue.initialValue, []); + const varFieldIntegerValue = new Variable( + "var2", + new ClassFieldAccess(varClass, "fieldInteger"), + ); + expectToBeType( + typir.Inference.inferType(varFieldIntegerValue), + isPrimitiveType, + (type) => type.getName() === "integer", + ); + expectValidationIssuesStrict( + typir, + varFieldIntegerValue.initialValue, + [], + ); // var3 := var1.fieldBoolean; - const varFieldBooleanValue = new Variable('var3', new ClassFieldAccess(varClass, 'fieldBoolean')); - expectToBeType(typir.Inference.inferType(varFieldBooleanValue), isPrimitiveType, type => type.getName() === 'boolean'); - expectValidationIssuesStrict(typir, varFieldBooleanValue.initialValue, ["Validated access of 'fieldBoolean' of the variable 'var1'."]); + const varFieldBooleanValue = new Variable( + "var3", + new ClassFieldAccess(varClass, "fieldBoolean"), + ); + expectToBeType( + typir.Inference.inferType(varFieldBooleanValue), + isPrimitiveType, + (type) => type.getName() === "boolean", + ); + expectValidationIssuesStrict(typir, varFieldBooleanValue.initialValue, [ + "Validated access of 'fieldBoolean' of the variable 'var1'.", + ]); }); - }); diff --git a/packages/typir/test/kinds/function/operator-inference-call.test.ts b/packages/typir/test/kinds/function/operator-inference-call.test.ts index 98143de1..d3f09ab8 100644 --- a/packages/typir/test/kinds/function/operator-inference-call.test.ts +++ b/packages/typir/test/kinds/function/operator-inference-call.test.ts @@ -4,16 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from 'vitest'; -import { isType } from '../../../src/graph/type-node.js'; -import { isPrimitiveType, PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; -import { BinaryExpression, InferenceRuleBinaryExpression, integer123, integer456, IntegerLiteral, string123, string456, StringLiteral, TestExpressionNode, TestLanguageNode } from '../../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../../src/typir.js'; -import { createTypirServicesForTesting, expectToBeType, expectValidationIssuesStrict } from '../../../src/utils/test-utils.js'; - -describe('Tests some special cases for (overloaded) operator calls', () => { - - describe('Overloaded operators, one signature has no arguments at all (this is tests explicitly cover a found bug)', () => { +import { beforeAll, describe, expect, test } from "vitest"; +import { isType } from "../../../src/graph/type-node.js"; +import { + isPrimitiveType, + PrimitiveType, +} from "../../../src/kinds/primitive/primitive-type.js"; +import { + BinaryExpression, + InferenceRuleBinaryExpression, + integer123, + integer456, + IntegerLiteral, + string123, + string456, + StringLiteral, + TestExpressionNode, + TestLanguageNode, +} from "../../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../../src/typir.js"; +import { + createTypirServicesForTesting, + expectToBeType, + expectValidationIssuesStrict, +} from "../../../src/utils/test-utils.js"; + +describe("Tests some special cases for (overloaded) operator calls", () => { + describe("Overloaded operators, one signature has no arguments at all (this is tests explicitly cover a found bug)", () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -22,92 +39,196 @@ describe('Tests some special cases for (overloaded) operator calls', () => { typir = createTypirServicesForTesting(); // primitive types - integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); + integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ + filter: (node) => node instanceof IntegerLiteral, + }) + .finish(); + stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ + filter: (node) => node instanceof StringLiteral, + }) + .finish(); // + operator: is overloaded // integers, without arguments - typir.factory.Operators.createBinary({ name: '+', signature: { left: integerType, right: integerType, return: integerType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, operands: () => []}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: integerType, + right: integerType, + return: integerType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + operands: () => [], + }) // .inferenceRule(inferenceRule) .finish(); // strings, with arguments - typir.factory.Operators.createBinary({ name: '+', signature: { left: stringType, right: stringType, return: stringType }}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: stringType, + right: stringType, + return: stringType, + }, + }) .inferenceRule(InferenceRuleBinaryExpression) .finish(); }); - - test('+ with Strings', () => { - expectInferredType(typir, string123, '+', string456, 'string'); + test("+ with Strings", () => { + expectInferredType(typir, string123, "+", string456, "string"); }); - test('+ with Integers', () => { + test("+ with Integers", () => { // fails, since the signature expects two arguments, but the inference rule provides no arguments - expectInferenceProblem(typir, integer123, '+', integer456); + expectInferenceProblem(typir, integer123, "+", integer456); }); }); - test('Overloaded operator has two inference rules', () => { + test("Overloaded operator has two inference rules", () => { const typir = createTypirServicesForTesting(); // primitive types - const integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - const stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); + const integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + const stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ filter: (node) => node instanceof StringLiteral }) + .finish(); // + operator: is overloaded // integers, without arguments - typir.factory.Operators.createBinary({ name: '+', signature: { left: integerType, right: integerType, return: integerType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, operands: () => []}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: integerType, + right: integerType, + return: integerType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + operands: () => [], + }) .inferenceRule(InferenceRuleBinaryExpression) // this has a second inference rule .finish(); // strings, with arguments - typir.factory.Operators.createBinary({ name: '+', signature: { left: stringType, right: stringType, return: stringType }}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: stringType, + right: stringType, + return: stringType, + }, + }) .inferenceRule(InferenceRuleBinaryExpression) .finish(); // with the second inference rule, it works now as usual! - expectInferredType(typir, integer123, '+', integer456, 'integer'); + expectInferredType(typir, integer123, "+", integer456, "integer"); }); - test('Overloaded operator has a validation for the inference rule of one signature', () => { + test("Overloaded operator has a validation for the inference rule of one signature", () => { const typir = createTypirServicesForTesting(); // primitive types - const integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - const stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); + const integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + const stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ filter: (node) => node instanceof StringLiteral }) + .finish(); // + operator: is overloaded // integers, with validation - typir.factory.Operators.createBinary({ name: '+', signature: { left: integerType, right: integerType, return: integerType }}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: integerType, + right: integerType, + return: integerType, + }, + }) .inferenceRule({ ...InferenceRuleBinaryExpression, - validation: (node, name, opType, accept) => accept({ languageNode: node, severity: 'error', message: `Called '${name}' with '${opType.getOutput()!.type.getName()}'.` }), + validation: (node, name, opType, accept) => + accept({ + languageNode: node, + severity: "error", + message: `Called '${name}' with '${opType.getOutput()!.type.getName()}'.`, + }), }) .finish(); // strings, without validation - typir.factory.Operators.createBinary({ name: '+', signature: { left: stringType, right: stringType, return: stringType }}) + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: stringType, + right: stringType, + return: stringType, + }, + }) .inferenceRule(InferenceRuleBinaryExpression) .finish(); // validation issues only for one of the two signatures! - expectValidationIssuesStrict(typir, new BinaryExpression(integer123, '+', integer456), ["Called '+' with 'integer'."]); - expectValidationIssuesStrict(typir, new BinaryExpression(string123, '+', string456), []); + expectValidationIssuesStrict( + typir, + new BinaryExpression(integer123, "+", integer456), + ["Called '+' with 'integer'."], + ); + expectValidationIssuesStrict( + typir, + new BinaryExpression(string123, "+", string456), + [], + ); }); - }); -function expectInferredType(typir: TypirServices, left: TestExpressionNode, operator: '+', right: TestExpressionNode, expectedType: 'integer'|'string'): void { +function expectInferredType( + typir: TypirServices, + left: TestExpressionNode, + operator: "+", + right: TestExpressionNode, + expectedType: "integer" | "string", +): void { const expr = new BinaryExpression(left, operator, right); const result = typir.Inference.inferType(expr); if (isType(result)) { - expectToBeType(result, isPrimitiveType, result => result.getName() === expectedType); + expectToBeType( + result, + isPrimitiveType, + (result) => result.getName() === expectedType, + ); } else { - expect.fail(result.map(p => typir.Printer.printTypirProblem(p)).join('\n')); + expect.fail( + result.map((p) => typir.Printer.printTypirProblem(p)).join("\n"), + ); } } -function expectInferenceProblem(typir: TypirServices, left: TestExpressionNode, operator: '+', right: TestExpressionNode): void { +function expectInferenceProblem( + typir: TypirServices, + left: TestExpressionNode, + operator: "+", + right: TestExpressionNode, +): void { const expr = new BinaryExpression(left, operator, right); const result = typir.Inference.inferType(expr); if (isType(result)) { diff --git a/packages/typir/test/kinds/function/operator-overloaded.test.ts b/packages/typir/test/kinds/function/operator-overloaded.test.ts index cff237d0..b020019a 100644 --- a/packages/typir/test/kinds/function/operator-overloaded.test.ts +++ b/packages/typir/test/kinds/function/operator-overloaded.test.ts @@ -4,21 +4,47 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -/* eslint-disable @typescript-eslint/parameter-properties */ - -import { beforeAll, describe, expect, test } from 'vitest'; -import { Type } from '../../../src/graph/type-node.js'; -import { isPrimitiveType, PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; -import { isAssignabilityProblem, isAssignabilitySuccess } from '../../../src/services/assignability.js'; -import { ConversionEdge } from '../../../src/services/conversion.js'; -import { InferenceRuleNotApplicable } from '../../../src/services/inference.js'; -import { SubTypeEdge } from '../../../src/services/subtype.js'; -import { AssignmentStatement, BinaryExpression, booleanFalse, BooleanLiteral, booleanTrue, double2_0, double3_0, DoubleLiteral, InferenceRuleBinaryExpression, integer2, integer3, IntegerLiteral, string2, string3, StringLiteral, TestExpressionNode, TestLanguageNode, Variable } from '../../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../../src/typir.js'; -import { createTypirServicesForTesting, expectToBeType } from '../../../src/utils/test-utils.js'; -import { assertTrue } from '../../../src/utils/utils.js'; - -describe('Multiple best matches for overloaded operators', () => { +import { beforeAll, describe, expect, test } from "vitest"; +import { Type } from "../../../src/graph/type-node.js"; +import { + isPrimitiveType, + PrimitiveType, +} from "../../../src/kinds/primitive/primitive-type.js"; +import { + isAssignabilityProblem, + isAssignabilitySuccess, +} from "../../../src/services/assignability.js"; +import { ConversionEdge } from "../../../src/services/conversion.js"; +import { InferenceRuleNotApplicable } from "../../../src/services/inference.js"; +import { SubTypeEdge } from "../../../src/services/subtype.js"; +import { + AssignmentStatement, + BinaryExpression, + booleanFalse, + BooleanLiteral, + booleanTrue, + double2_0, + double3_0, + DoubleLiteral, + InferenceRuleBinaryExpression, + integer2, + integer3, + IntegerLiteral, + string2, + string3, + StringLiteral, + TestExpressionNode, + TestLanguageNode, + Variable, +} from "../../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../../src/typir.js"; +import { + createTypirServicesForTesting, + expectToBeType, +} from "../../../src/utils/test-utils.js"; +import { assertTrue } from "../../../src/utils/utils.js"; + +describe("Multiple best matches for overloaded operators", () => { let typir: TypirServices; let integerType: PrimitiveType; let doubleType: PrimitiveType; @@ -29,26 +55,56 @@ describe('Multiple best matches for overloaded operators', () => { typir = createTypirServicesForTesting(); // primitive types - integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - doubleType = typir.factory.Primitives.create({ primitiveName: 'double' }).inferenceRule({ filter: node => node instanceof DoubleLiteral }).finish(); - stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); - booleanType = typir.factory.Primitives.create({ primitiveName: 'boolean' }).inferenceRule({ filter: node => node instanceof BooleanLiteral }).finish(); + integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + doubleType = typir.factory.Primitives.create({ + primitiveName: "double", + }) + .inferenceRule({ filter: (node) => node instanceof DoubleLiteral }) + .finish(); + stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ filter: (node) => node instanceof StringLiteral }) + .finish(); + booleanType = typir.factory.Primitives.create({ + primitiveName: "boolean", + }) + .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) + .finish(); // operators - typir.factory.Operators.createBinary({ name: '+', signatures: [ // operator overloading - { left: integerType, right: integerType, return: integerType }, // 2 + 3 => 5 - { left: doubleType, right: doubleType, return: doubleType }, // 2.0 + 3.0 => 5.0 - { left: stringType, right: stringType, return: stringType }, // "2" + "3" => "23" - { left: booleanType, right: booleanType, return: booleanType }, // TRUE + TRUE => FALSE - ] }).inferenceRule(InferenceRuleBinaryExpression).finish(); + typir.factory.Operators.createBinary({ + name: "+", + signatures: [ + // operator overloading + { left: integerType, right: integerType, return: integerType }, // 2 + 3 => 5 + { left: doubleType, right: doubleType, return: doubleType }, // 2.0 + 3.0 => 5.0 + { left: stringType, right: stringType, return: stringType }, // "2" + "3" => "23" + { left: booleanType, right: booleanType, return: booleanType }, // TRUE + TRUE => FALSE + ], + }) + .inferenceRule(InferenceRuleBinaryExpression) + .finish(); // define relationships between types - typir.Conversion.markAsConvertible(booleanType, integerType, 'IMPLICIT_EXPLICIT'); // integerVariable := booleanValue; + typir.Conversion.markAsConvertible( + booleanType, + integerType, + "IMPLICIT_EXPLICIT", + ); // integerVariable := booleanValue; typir.Subtype.markAsSubType(integerType, doubleType); // double <|--- integer - typir.Conversion.markAsConvertible(doubleType, stringType, 'IMPLICIT_EXPLICIT'); // stringVariable := doubleValue; + typir.Conversion.markAsConvertible( + doubleType, + stringType, + "IMPLICIT_EXPLICIT", + ); // stringVariable := doubleValue; // specify, how Typir can detect the type of a variable - typir.Inference.addInferenceRule(node => { + typir.Inference.addInferenceRule((node) => { if (node instanceof Variable) { return node.initialValue; // the type of the variable is the type of its initial value } @@ -58,73 +114,103 @@ describe('Multiple best matches for overloaded operators', () => { // register a type-related validation typir.validation.Collector.addValidationRule((node, accept) => { if (node instanceof AssignmentStatement) { - typir.validation.Constraints.ensureNodeIsAssignable(node.right, node.left, accept, (actual, expected) => ({ message: - `The type '${actual.name}' is not assignable to the type '${expected.name}'.` })); + typir.validation.Constraints.ensureNodeIsAssignable( + node.right, + node.left, + accept, + (actual, expected) => ({ + message: `The type '${actual.name}' is not assignable to the type '${expected.name}'.`, + }), + ); } }); }); - - describe('tests all cases for assignability and the checks the found assignability paths', () => { - test('integer to integer', () => { + describe("tests all cases for assignability and the checks the found assignability paths", () => { + test("integer to integer", () => { expectAssignmentValid(integerType, integerType); }); - test('double to integer', () => { + test("double to integer", () => { expectAssignmentError(doubleType, integerType); }); - test('string to integer', () => { + test("string to integer", () => { expectAssignmentError(stringType, integerType); }); - test('boolean to integer', () => { - expectAssignmentValid(booleanType, integerType, 'ConversionEdge'); + test("boolean to integer", () => { + expectAssignmentValid(booleanType, integerType, "ConversionEdge"); }); - test('integer to double', () => { - expectAssignmentValid(integerType, doubleType, 'SubTypeEdge'); + test("integer to double", () => { + expectAssignmentValid(integerType, doubleType, "SubTypeEdge"); }); - test('double to double', () => { + test("double to double", () => { expectAssignmentValid(doubleType, doubleType); }); - test('string to double', () => { + test("string to double", () => { expectAssignmentError(stringType, doubleType); }); - test('boolean to double', () => { - expectAssignmentValid(booleanType, doubleType, 'ConversionEdge', 'SubTypeEdge'); + test("boolean to double", () => { + expectAssignmentValid( + booleanType, + doubleType, + "ConversionEdge", + "SubTypeEdge", + ); }); - test('integer to string', () => { - expectAssignmentValid(integerType, stringType, 'SubTypeEdge', 'ConversionEdge'); + test("integer to string", () => { + expectAssignmentValid( + integerType, + stringType, + "SubTypeEdge", + "ConversionEdge", + ); }); - test('double to string', () => { - expectAssignmentValid(doubleType, stringType, 'ConversionEdge'); + test("double to string", () => { + expectAssignmentValid(doubleType, stringType, "ConversionEdge"); }); - test('string to string', () => { + test("string to string", () => { expectAssignmentValid(stringType, stringType); }); - test('boolean to string', () => { - expectAssignmentValid(booleanType, stringType, 'ConversionEdge', 'SubTypeEdge', 'ConversionEdge'); + test("boolean to string", () => { + expectAssignmentValid( + booleanType, + stringType, + "ConversionEdge", + "SubTypeEdge", + "ConversionEdge", + ); }); - test('integer to boolean', () => { + test("integer to boolean", () => { expectAssignmentError(integerType, booleanType); }); - test('double to boolean', () => { + test("double to boolean", () => { expectAssignmentError(doubleType, booleanType); }); - test('string to boolean', () => { + test("string to boolean", () => { expectAssignmentError(stringType, booleanType); }); - test('boolean to boolean', () => { + test("boolean to boolean", () => { expectAssignmentValid(booleanType, booleanType); }); - - function expectAssignmentValid(sourceType: Type, targetType: Type, ...expectedPath: Array): void { + function expectAssignmentValid( + sourceType: Type, + targetType: Type, + ...expectedPath: Array< + SubTypeEdge["$relation"] | ConversionEdge["$relation"] + > + ): void { // check the resulting assignability path - const assignabilityResult = typir.Assignability.getAssignabilityResult(sourceType, targetType); + const assignabilityResult = + typir.Assignability.getAssignabilityResult( + sourceType, + targetType, + ); assertTrue(isAssignabilitySuccess(assignabilityResult)); const actualPath = assignabilityResult.path; - const msg = `Actual assignability path is ${actualPath.map(e => e.$relation).join(' --> ')}.`; + const msg = `Actual assignability path is ${actualPath.map((e) => e.$relation).join(" --> ")}.`; expect(actualPath.length, msg).toBe(expectedPath.length); for (let i = 0; i < actualPath.length; i++) { expect(actualPath[i].$relation, msg).toBe(expectedPath[i]); @@ -140,55 +226,72 @@ describe('Multiple best matches for overloaded operators', () => { } } - function expectAssignmentError(sourceType: Type, targetType: Type): void { - const assignabilityResult = typir.Assignability.getAssignabilityResult(sourceType, targetType); + function expectAssignmentError( + sourceType: Type, + targetType: Type, + ): void { + const assignabilityResult = + typir.Assignability.getAssignabilityResult( + sourceType, + targetType, + ); assertTrue(isAssignabilityProblem(assignabilityResult)); } }); - - describe('Test multiple matches for overloaded operators and ensures that the best match is chosen', () => { - test('2 + 3 => both are integers', () => { - expectOverload(integer2, integer3, 'integer'); + describe("Test multiple matches for overloaded operators and ensures that the best match is chosen", () => { + test("2 + 3 => both are integers", () => { + expectOverload(integer2, integer3, "integer"); }); - test('2.0 + 3.0 => both are doubles', () => { - expectOverload(double2_0, double3_0, 'double'); + test("2.0 + 3.0 => both are doubles", () => { + expectOverload(double2_0, double3_0, "double"); }); test('"2" + "3" => both are strings', () => { - expectOverload(string2, string3, 'string'); + expectOverload(string2, string3, "string"); }); - test('TRUE + FALSE => both are booleans', () => { - expectOverload(booleanTrue, booleanFalse, 'boolean'); + test("TRUE + FALSE => both are booleans", () => { + expectOverload(booleanTrue, booleanFalse, "boolean"); }); - test('2 + TRUE => convert boolean to integer', () => { - expectOverload(integer2, booleanTrue, 'integer'); + test("2 + TRUE => convert boolean to integer", () => { + expectOverload(integer2, booleanTrue, "integer"); }); - test('2.0 + 3 => integers are doubles', () => { - expectOverload(double2_0, integer3, 'double'); + test("2.0 + 3 => integers are doubles", () => { + expectOverload(double2_0, integer3, "double"); }); test('2.0 + "3" => convert double to string', () => { - expectOverload(double2_0, string3, 'string'); + expectOverload(double2_0, string3, "string"); }); test('2 + "3" => integer is sub-type of double, which is convertible to string', () => { - expectOverload(integer2, string3, 'string'); + expectOverload(integer2, string3, "string"); }); - - function expectOverload(left: TestExpressionNode, right: TestExpressionNode, typeName: 'string'|'integer'|'double'|'boolean'): void { - const example = new BinaryExpression(left, '+', right); - const validationProblems = typir.validation.Collector.validate(example); - expect(validationProblems, validationProblems.map(p => typir.Printer.printValidationProblem(p)).join('\n')).toHaveLength(0); + function expectOverload( + left: TestExpressionNode, + right: TestExpressionNode, + typeName: "string" | "integer" | "double" | "boolean", + ): void { + const example = new BinaryExpression(left, "+", right); + const validationProblems = + typir.validation.Collector.validate(example); + expect( + validationProblems, + validationProblems + .map((p) => typir.Printer.printValidationProblem(p)) + .join("\n"), + ).toHaveLength(0); const inferredType = typir.Inference.inferType(example); - expectToBeType(inferredType, isPrimitiveType, type => type.getName() === typeName); + expectToBeType( + inferredType, + isPrimitiveType, + (type) => type.getName() === typeName, + ); } }); - }); - diff --git a/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts b/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts index 834c8222..78eb1341 100644 --- a/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts +++ b/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts @@ -4,11 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from 'vitest'; -import { PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; -import { BinaryExpression, booleanFalse, BooleanLiteral, booleanTrue, InferenceRuleBinaryExpression, integer123, integer456, IntegerLiteral, TestExpressionNode, TestLanguageNode } from '../../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../../src/typir.js'; -import { createTypirServicesForTesting } from '../../../src/utils/test-utils.js'; +import { beforeAll, describe, expect, test } from "vitest"; +import { PrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +import { + BinaryExpression, + booleanFalse, + BooleanLiteral, + booleanTrue, + InferenceRuleBinaryExpression, + integer123, + integer456, + IntegerLiteral, + TestExpressionNode, + TestLanguageNode, +} from "../../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../../src/typir.js"; +import { createTypirServicesForTesting } from "../../../src/utils/test-utils.js"; describe('Tests the "validateArgumentsOfCalls" option to check the given arguments in (overloaded) operator calls', () => { let typir: TypirServices; @@ -19,62 +30,136 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen typir = createTypirServicesForTesting(); // primitive types - integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - booleanType = typir.factory.Primitives.create({ primitiveName: 'boolean' }).inferenceRule({ filter: node => node instanceof BooleanLiteral }).finish(); + integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + booleanType = typir.factory.Primitives.create({ + primitiveName: "boolean", + }) + .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) + .finish(); // + operator: only integers, validate it - typir.factory.Operators.createBinary({ name: '+', signature: { left: integerType, right: integerType, return: integerType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, validateArgumentsOfCalls: true }).finish(); + typir.factory.Operators.createBinary({ + name: "+", + signature: { + left: integerType, + right: integerType, + return: integerType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + validateArgumentsOfCalls: true, + }) + .finish(); // && operator: only booleans, don't validate it - typir.factory.Operators.createBinary({ name: '&&', signature: { left: booleanType, right: booleanType, return: booleanType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, validateArgumentsOfCalls: false }).finish(); + typir.factory.Operators.createBinary({ + name: "&&", + signature: { + left: booleanType, + right: booleanType, + return: booleanType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + validateArgumentsOfCalls: false, + }) + .finish(); // == operator: is overloaded, validate the integer signature, don't validate the boolean signature - typir.factory.Operators.createBinary({ name: '==', signature: { left: integerType, right: integerType, return: booleanType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, validateArgumentsOfCalls: true }).finish(); - typir.factory.Operators.createBinary({ name: '==', signature: { left: booleanType, right: booleanType, return: booleanType }}) - .inferenceRule({ ...InferenceRuleBinaryExpression, validateArgumentsOfCalls: false }).finish(); + typir.factory.Operators.createBinary({ + name: "==", + signature: { + left: integerType, + right: integerType, + return: booleanType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + validateArgumentsOfCalls: true, + }) + .finish(); + typir.factory.Operators.createBinary({ + name: "==", + signature: { + left: booleanType, + right: booleanType, + return: booleanType, + }, + }) + .inferenceRule({ + ...InferenceRuleBinaryExpression, + validateArgumentsOfCalls: false, + }) + .finish(); }); - // +: only integers are supported - test('123 + 456: OK', () => { - expectOperatorCallValid(integer123, '+', integer456); + test("123 + 456: OK", () => { + expectOperatorCallValid(integer123, "+", integer456); }); - test('true + false: wrong, since this signature does not exist', () => { - expectOperatorCallError(booleanTrue, '+', booleanFalse, "The type 'boolean' is not assignable to the type 'integer'."); + test("true + false: wrong, since this signature does not exist", () => { + expectOperatorCallError( + booleanTrue, + "+", + booleanFalse, + "The type 'boolean' is not assignable to the type 'integer'.", + ); }); // &&: only booleans are supported - test('123 && 456: not OK, but no errors are shown, since the validation is switched off for &&', () => { - expectOperatorCallValid(integer123, '&&', integer456); + test("123 && 456: not OK, but no errors are shown, since the validation is switched off for &&", () => { + expectOperatorCallValid(integer123, "&&", integer456); }); - test('true && false: OK', () => { - expectOperatorCallValid(booleanTrue, '&&', booleanFalse); + test("true && false: OK", () => { + expectOperatorCallValid(booleanTrue, "&&", booleanFalse); }); // ==: both signatures are supported, but only one is validated - test('123 == 456: OK and validated', () => { - expectOperatorCallValid(integer123, '==', integer456); + test("123 == 456: OK and validated", () => { + expectOperatorCallValid(integer123, "==", integer456); }); - test('true == false: OK, since the signature exists', () => { - expectOperatorCallValid(booleanTrue, '==', booleanFalse); + test("true == false: OK, since the signature exists", () => { + expectOperatorCallValid(booleanTrue, "==", booleanFalse); }); - test('123 == false: wrong, since this signature is not defined', () => { - expectOperatorCallError(integer123, '==', booleanFalse, 'is not assignable to the type'); + test("123 == false: wrong, since this signature is not defined", () => { + expectOperatorCallError( + integer123, + "==", + booleanFalse, + "is not assignable to the type", + ); }); - test('true == 456: wrong, since this signature is not defined', () => { - expectOperatorCallError(booleanTrue, '==', integer456, 'is not assignable to the type'); + test("true == 456: wrong, since this signature is not defined", () => { + expectOperatorCallError( + booleanTrue, + "==", + integer456, + "is not assignable to the type", + ); }); - - function expectOperatorCallValid(left: TestExpressionNode, operator: '=='|'+'|'&&', right: TestExpressionNode): void { + function expectOperatorCallValid( + left: TestExpressionNode, + operator: "==" | "+" | "&&", + right: TestExpressionNode, + ): void { const expr = new BinaryExpression(left, operator, right); const result = typir.validation.Collector.validate(expr); expect(result).toHaveLength(0); } - function expectOperatorCallError(left: TestExpressionNode, operator: '=='|'+'|'&&', right: TestExpressionNode, includedProblem: string): void { + function expectOperatorCallError( + left: TestExpressionNode, + operator: "==" | "+" | "&&", + right: TestExpressionNode, + includedProblem: string, + ): void { const expr = new BinaryExpression(left, operator, right); const result = typir.validation.Collector.validate(expr); expect(result.length === 1).toBeTruthy(); @@ -82,5 +167,4 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen expect(msg, msg).includes(`'${operator}'`); expect(msg, msg).includes(includedProblem); } - }); diff --git a/packages/typir/test/kinds/primitive/primitive.test.ts b/packages/typir/test/kinds/primitive/primitive.test.ts index b1d18170..d92e351e 100644 --- a/packages/typir/test/kinds/primitive/primitive.test.ts +++ b/packages/typir/test/kinds/primitive/primitive.test.ts @@ -4,63 +4,95 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeEach, describe, expect, test } from 'vitest'; -import { createTypirServicesForTesting, expectTypirTypes } from '../../../src/utils/test-utils.js'; -import { assertTypirType } from '../../../src/utils/utils.js'; -import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; -import { integer123, IntegerLiteral, stringHello, StringLiteral, TestLanguageNode } from '../../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../../src/typir.js'; +import { beforeEach, describe, expect, test } from "vitest"; +import { + createTypirServicesForTesting, + expectTypirTypes, +} from "../../../src/utils/test-utils.js"; +import { assertTypirType } from "../../../src/utils/utils.js"; +import { isPrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +import { + integer123, + IntegerLiteral, + stringHello, + StringLiteral, + TestLanguageNode, +} from "../../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../../src/typir.js"; -describe('Tests some details for primitive types', () => { - - test('create primitive and get it by name', () => { +describe("Tests some details for primitive types", () => { + test("create primitive and get it by name", () => { const typir = createTypirServicesForTesting(); - const integerType1 = typir.factory.Primitives.create({ primitiveName: 'integer' }).finish(); - assertTypirType(integerType1, isPrimitiveType, 'integer'); - expectTypirTypes(typir, isPrimitiveType, 'integer'); - const integerType2 = typir.factory.Primitives.get({ primitiveName: 'integer' }); - assertTypirType(integerType2, isPrimitiveType, 'integer'); + const integerType1 = typir.factory.Primitives.create({ + primitiveName: "integer", + }).finish(); + assertTypirType(integerType1, isPrimitiveType, "integer"); + expectTypirTypes(typir, isPrimitiveType, "integer"); + const integerType2 = typir.factory.Primitives.get({ + primitiveName: "integer", + }); + assertTypirType(integerType2, isPrimitiveType, "integer"); expect(integerType1).toBe(integerType2); }); - test('error when trying to create the same primitive twice', () => { + test("error when trying to create the same primitive twice", () => { const typir = createTypirServicesForTesting(); // create the 1st integer - const integerType1 = typir.factory.Primitives.create({ primitiveName: 'integer' }).finish(); - assertTypirType(integerType1, isPrimitiveType, 'integer'); + const integerType1 = typir.factory.Primitives.create({ + primitiveName: "integer", + }).finish(); + assertTypirType(integerType1, isPrimitiveType, "integer"); // creating the 2nd integer will fail - expect(() => typir.factory.Primitives.create({ primitiveName: 'integer' }).finish()) - .toThrowError(); + expect(() => + typir.factory.Primitives.create({ + primitiveName: "integer", + }).finish(), + ).toThrowError(); }); - describe('Test validation for inference rule of a primitive type', () => { + describe("Test validation for inference rule of a primitive type", () => { let typir: TypirServices; beforeEach(() => { typir = createTypirServicesForTesting(); // create a primitive type with some inference rules - typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ - // 1st rule for IntegerLiterals, with validation - languageKey: IntegerLiteral.name, - validation: (node: IntegerLiteral, type, accept) => accept({ message: 'integer-validation', languageNode: node, severity: 'error' }), - }).inferenceRule({ - // 2nd rule for StringLiterals (which does not make sense, just for testing), without validation - languageKey: StringLiteral.name, - }).finish(); + typir.factory.Primitives.create({ primitiveName: "integer" }) + .inferenceRule({ + // 1st rule for IntegerLiterals, with validation + languageKey: IntegerLiteral.name, + validation: (node: IntegerLiteral, type, accept) => + accept({ + message: "integer-validation", + languageNode: node, + severity: "error", + }), + }) + .inferenceRule({ + // 2nd rule for StringLiterals (which does not make sense, just for testing), without validation + languageKey: StringLiteral.name, + }) + .finish(); }); - test('Integer value with validation issues', () => { - assertTypirType(typir.Inference.inferType(integer123), isPrimitiveType, 'integer'); // test the successful inference + test("Integer value with validation issues", () => { + assertTypirType( + typir.Inference.inferType(integer123), + isPrimitiveType, + "integer", + ); // test the successful inference const result = typir.validation.Collector.validate(integer123); // check that a validation issue is produced expect(result).toHaveLength(1); - expect(result[0].message).toBe('integer-validation'); + expect(result[0].message).toBe("integer-validation"); }); - test('String value without validation issue', () => { - assertTypirType(typir.Inference.inferType(stringHello), isPrimitiveType, 'integer'); // test the successful inference + test("String value without validation issue", () => { + assertTypirType( + typir.Inference.inferType(stringHello), + isPrimitiveType, + "integer", + ); // test the successful inference const result = typir.validation.Collector.validate(stringHello); // check that no validation issue is produced expect(result).toHaveLength(0); }); }); - }); diff --git a/packages/typir/test/services/conversion.test.ts b/packages/typir/test/services/conversion.test.ts index f41466ec..8110a444 100644 --- a/packages/typir/test/services/conversion.test.ts +++ b/packages/typir/test/services/conversion.test.ts @@ -4,20 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, expect, test } from 'vitest'; -import { createTypirServicesForTesting } from '../../src/utils/test-utils.js'; +import { describe, expect, test } from "vitest"; +import { createTypirServicesForTesting } from "../../src/utils/test-utils.js"; -describe('Testing conversion', () => { - - test('exception in case of cyclic conversion rules', () => { +describe("Testing conversion", () => { + test("exception in case of cyclic conversion rules", () => { const typir = createTypirServicesForTesting(); - const integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).finish(); - const doubleType = typir.factory.Primitives.create({ primitiveName: 'double' }).finish(); + const integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }).finish(); + const doubleType = typir.factory.Primitives.create({ + primitiveName: "double", + }).finish(); // define cyclic relationships between types - typir.Conversion.markAsConvertible(integerType, doubleType, 'IMPLICIT_EXPLICIT'); - expect(() => typir.Conversion.markAsConvertible(doubleType, integerType, 'IMPLICIT_EXPLICIT')) - .toThrowError('Adding the conversion from double to integer with mode IMPLICIT_EXPLICIT has introduced a cycle in the type graph.'); + typir.Conversion.markAsConvertible( + integerType, + doubleType, + "IMPLICIT_EXPLICIT", + ); + expect(() => + typir.Conversion.markAsConvertible( + doubleType, + integerType, + "IMPLICIT_EXPLICIT", + ), + ).toThrowError( + "Adding the conversion from double to integer with mode IMPLICIT_EXPLICIT has introduced a cycle in the type graph.", + ); }); - }); diff --git a/packages/typir/test/services/inference-registry.test.ts b/packages/typir/test/services/inference-registry.test.ts index 227605e5..ca4c42b0 100644 --- a/packages/typir/test/services/inference-registry.test.ts +++ b/packages/typir/test/services/inference-registry.test.ts @@ -2,19 +2,33 @@ * Copyright 2025 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ - -import { beforeEach, describe, expect, test } from 'vitest'; -import { isType, Type } from '../../src/graph/type-node.js'; -import { PrimitiveType } from '../../src/kinds/primitive/primitive-type.js'; -import { CompositeTypeInferenceRule, DefaultTypeInferenceCollector, InferenceProblem, InferenceRuleNotApplicable, TypeInferenceRule, TypeInferenceRuleWithoutInferringChildren } from '../../src/services/inference.js'; -import { ValidationRuleOptions } from '../../src/services/validation.js'; -import { booleanFalse, integer123, IntegerLiteral, stringHello, StringLiteral, TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../src/typir.js'; -import { RuleRegistry } from '../../src/utils/rule-registration.js'; -import { createTypirServicesForTesting } from '../../src/utils/test-utils.js'; - -describe('Tests the logic for registering rules (applied to inference rules)', () => { + ******************************************************************************/ + +import { beforeEach, describe, expect, test } from "vitest"; +import { isType, Type } from "../../src/graph/type-node.js"; +import { PrimitiveType } from "../../src/kinds/primitive/primitive-type.js"; +import { + CompositeTypeInferenceRule, + DefaultTypeInferenceCollector, + InferenceProblem, + InferenceRuleNotApplicable, + TypeInferenceRule, + TypeInferenceRuleWithoutInferringChildren, +} from "../../src/services/inference.js"; +import { ValidationRuleOptions } from "../../src/services/validation.js"; +import { + booleanFalse, + integer123, + IntegerLiteral, + stringHello, + StringLiteral, + TestLanguageNode, +} from "../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../src/typir.js"; +import { RuleRegistry } from "../../src/utils/rule-registration.js"; +import { createTypirServicesForTesting } from "../../src/utils/test-utils.js"; + +describe("Tests the logic for registering rules (applied to inference rules)", () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -22,7 +36,7 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( let ruleString: TypeInferenceRuleWithoutInferringChildren; let ruleInteger: TypeInferenceRuleWithoutInferringChildren; let ruleStringInteger: TypeInferenceRuleWithoutInferringChildren; - const NOT_FOUND = 'found no applicable inference rules'; + const NOT_FOUND = "found no applicable inference rules"; beforeEach(() => { // Typir services @@ -31,28 +45,32 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( }); // primitive types - integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).finish(); - stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).finish(); + integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }).finish(); + stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }).finish(); // composite inference rules composite = new CompositeTypeInferenceRule(typir, typir.Inference); // validation rules - ruleString = node => { + ruleString = (node) => { if (node instanceof StringLiteral) { return stringType; } else { return InferenceRuleNotApplicable; } }; - ruleInteger = node => { + ruleInteger = (node) => { if (node instanceof IntegerLiteral) { return integerType; } else { return InferenceRuleNotApplicable; } }; - ruleStringInteger = node => { + ruleStringInteger = (node) => { if (node instanceof StringLiteral) { return stringType; } else if (node instanceof IntegerLiteral) { @@ -61,24 +79,22 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( return { $problem: InferenceProblem, languageNode: node, - location: 'failure3-' + node.print(), + location: "failure3-" + node.print(), subProblems: [], }; } }; }); - - describe('Simple inference rules', () => { - - test('add String rule without any options', () => { + describe("Simple inference rules", () => { + test("add String rule without any options", () => { assertNumberRules(0); addInferenceRule(ruleString); assertNumberRules(1); infer(stringHello, stringType); }); - test('remove String rule without any options', () => { + test("remove String rule without any options", () => { addInferenceRule(ruleString); infer(stringHello, stringType); removeInferenceRule(ruleString); @@ -86,51 +102,61 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( infer(stringHello, NOT_FOUND); }); - test('add rule for Strings only', () => { - addInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name }); + test("add rule for Strings only", () => { + addInferenceRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); infer(stringHello, stringType); infer(integer123, NOT_FOUND); infer(booleanFalse, NOT_FOUND); }); - test('add rule for String and Integer', () => { - addInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name }); + test("add rule for String and Integer", () => { + addInferenceRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); infer(stringHello, stringType); infer(integer123, NOT_FOUND); infer(booleanFalse, NOT_FOUND); - addInferenceRule(ruleStringInteger, { languageKey: IntegerLiteral.name }); + addInferenceRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); infer(stringHello, stringType); infer(integer123, integerType); infer(booleanFalse, NOT_FOUND); }); - test('remove rule', () => { - addInferenceRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name] }); + test("remove rule", () => { + addInferenceRule(ruleStringInteger, { + languageKey: [StringLiteral.name, IntegerLiteral.name], + }); infer(stringHello, stringType); infer(integer123, integerType); infer(booleanFalse, NOT_FOUND); - removeInferenceRule(ruleStringInteger, { languageKey: IntegerLiteral.name }); + removeInferenceRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); infer(stringHello, stringType); infer(integer123, NOT_FOUND); infer(booleanFalse, NOT_FOUND); - removeInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name }); + removeInferenceRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); infer(stringHello, NOT_FOUND); infer(integer123, NOT_FOUND); infer(booleanFalse, NOT_FOUND); }); - }); - describe('Composite inference rule', () => { - - test('add String rule to composite', () => { + describe("Composite inference rule", () => { + test("add String rule to composite", () => { assertNumberRules(0); composite.addInferenceRule(ruleString); assertNumberRules(1); infer(stringHello, stringType); }); - test('remove String rule from composite', () => { + test("remove String rule from composite", () => { composite.addInferenceRule(ruleString); infer(stringHello, stringType); composite.removeInferenceRule(ruleString); @@ -154,28 +180,36 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( infer(integer123, NOT_FOUND); }); - test('remove rule with multiple keys from composite', () => { - composite.addInferenceRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name] }); + test("remove rule with multiple keys from composite", () => { + composite.addInferenceRule(ruleStringInteger, { + languageKey: [StringLiteral.name, IntegerLiteral.name], + }); assertNumberRules(1); infer(stringHello, stringType); infer(integer123, integerType); - composite.removeInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name }); + composite.removeInferenceRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); assertNumberRules(1); infer(stringHello, NOT_FOUND); infer(integer123, integerType); - composite.removeInferenceRule(ruleStringInteger, { languageKey: IntegerLiteral.name }); + composite.removeInferenceRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); assertNumberRules(0); infer(stringHello, NOT_FOUND); infer(integer123, NOT_FOUND); }); - test('remove rules which are bound to types from composite', () => { + test("remove rules which are bound to types from composite", () => { assertNumberRules(0); composite.addInferenceRule(ruleString, { boundToType: stringType }); assertNumberRules(1); infer(stringHello, stringType); infer(integer123, NOT_FOUND); - composite.addInferenceRule(ruleInteger, { boundToType: integerType }); + composite.addInferenceRule(ruleInteger, { + boundToType: integerType, + }); assertNumberRules(1); infer(stringHello, stringType); infer(integer123, integerType); @@ -189,9 +223,11 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( infer(integer123, NOT_FOUND); }); - test('remove rule which is bound to types from composite', () => { + test("remove rule which is bound to types from composite", () => { assertNumberRules(0); - composite.addInferenceRule(ruleStringInteger, { boundToType: [stringType, integerType] }); + composite.addInferenceRule(ruleStringInteger, { + boundToType: [stringType, integerType], + }); assertNumberRules(1); infer(stringHello, stringType); infer(integer123, integerType); @@ -204,17 +240,21 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( infer(stringHello, NOT_FOUND); infer(integer123, NOT_FOUND); }); - }); - function removeType(type: Type): void { typir.infrastructure.Graph.removeNode(type); } - function addInferenceRule(rule: TypeInferenceRuleWithoutInferringChildren, options?: Partial) { + function addInferenceRule( + rule: TypeInferenceRuleWithoutInferringChildren, + options?: Partial, + ) { typir.Inference.addInferenceRule(rule, options); } - function removeInferenceRule(rule: TypeInferenceRuleWithoutInferringChildren, options?: Partial) { + function removeInferenceRule( + rule: TypeInferenceRuleWithoutInferringChildren, + options?: Partial, + ) { typir.Inference.removeInferenceRule(rule, options); } @@ -227,21 +267,33 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( const actual = typir.Inference.inferType(node); if (isType(actual)) { if (isType(expected)) { - const equal = typir.Equality.getTypeEqualityProblem(actual, expected); + const equal = typir.Equality.getTypeEqualityProblem( + actual, + expected, + ); if (equal === undefined) { // that is fine } else { expect.fail(typir.Printer.printTypirProblem(equal)); } } else { - expect.fail(`Got type '${actual.getName()}', but expected error "${expected}"`); + expect.fail( + `Got type '${actual.getName()}', but expected error "${expected}"`, + ); } } else { - const actualProblems = actual.map(a => typir.Printer.printTypirProblem(a)).join('\n'); + const actualProblems = actual + .map((a) => typir.Printer.printTypirProblem(a)) + .join("\n"); if (isType(expected)) { - expect.fail(`Got error "${actualProblems}", but expected type '${expected.getName()}'.`); + expect.fail( + `Got error "${actualProblems}", but expected type '${expected.getName()}'.`, + ); } else { - expect(actualProblems.includes(expected), actualProblems).toBeTruthy(); + expect( + actualProblems.includes(expected), + actualProblems, + ).toBeTruthy(); } } } @@ -249,5 +301,8 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( class TestInferenceImpl extends DefaultTypeInferenceCollector { // make the public to access their details - override readonly ruleRegistry: RuleRegistry, TestLanguageNode>; + override readonly ruleRegistry: RuleRegistry< + TypeInferenceRule, + TestLanguageNode + >; } diff --git a/packages/typir/test/services/validation-registry.test.ts b/packages/typir/test/services/validation-registry.test.ts index d1243efd..498d3aa0 100644 --- a/packages/typir/test/services/validation-registry.test.ts +++ b/packages/typir/test/services/validation-registry.test.ts @@ -2,18 +2,34 @@ * Copyright 2025 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ - -import { beforeEach, describe, expect, test } from 'vitest'; -import { Type } from '../../src/graph/type-node.js'; -import { PrimitiveType } from '../../src/kinds/primitive/primitive-type.js'; -import { DefaultValidationCollector, ValidationRule, ValidationRuleFunctional, ValidationRuleLifecycle, ValidationRuleOptions } from '../../src/services/validation.js'; -import { booleanTrue, integer123, IntegerLiteral, stringHello, StringLiteral, TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../src/typir.js'; -import { RuleRegistry } from '../../src/utils/rule-registration.js'; -import { createTypirServicesForTesting, expectValidationIssuesStrict } from '../../src/utils/test-utils.js'; - -describe('Tests the logic for registering rules (applied to state-less validation rules)', () => { + ******************************************************************************/ + +import { beforeEach, describe, expect, test } from "vitest"; +import { Type } from "../../src/graph/type-node.js"; +import { PrimitiveType } from "../../src/kinds/primitive/primitive-type.js"; +import { + DefaultValidationCollector, + ValidationRule, + ValidationRuleFunctional, + ValidationRuleLifecycle, + ValidationRuleOptions, +} from "../../src/services/validation.js"; +import { + booleanTrue, + integer123, + IntegerLiteral, + stringHello, + StringLiteral, + TestLanguageNode, +} from "../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../src/typir.js"; +import { RuleRegistry } from "../../src/utils/rule-registration.js"; +import { + createTypirServicesForTesting, + expectValidationIssuesStrict, +} from "../../src/utils/test-utils.js"; + +describe("Tests the logic for registering rules (applied to state-less validation rules)", () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -26,193 +42,256 @@ describe('Tests the logic for registering rules (applied to state-less validatio typir = createTypirServicesForTesting({ validation: { Collector: (services) => new TestValidatorImpl(services), - } + }, }); // primitive types - integerType = typir.factory.Primitives.create({ primitiveName: 'integer' }).inferenceRule({ filter: node => node instanceof IntegerLiteral }).finish(); - stringType = typir.factory.Primitives.create({ primitiveName: 'string' }).inferenceRule({ filter: node => node instanceof StringLiteral }).finish(); + integerType = typir.factory.Primitives.create({ + primitiveName: "integer", + }) + .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) + .finish(); + stringType = typir.factory.Primitives.create({ + primitiveName: "string", + }) + .inferenceRule({ filter: (node) => node instanceof StringLiteral }) + .finish(); // validation rules ruleString = (node, accept) => { if (node instanceof StringLiteral) { - accept({ languageNode: node, severity: 'error', message: `s1-${node.value}` }); + accept({ + languageNode: node, + severity: "error", + message: `s1-${node.value}`, + }); } }; ruleInteger = (node, accept) => { if (node instanceof IntegerLiteral) { - accept({ languageNode: node, severity: 'error', message: `i2-${node.value}` }); + accept({ + languageNode: node, + severity: "error", + message: `i2-${node.value}`, + }); } }; ruleStringInteger = (node, accept) => { if (node instanceof StringLiteral) { - accept({ languageNode: node, severity: 'error', message: `s3-${node.value}` }); + accept({ + languageNode: node, + severity: "error", + message: `s3-${node.value}`, + }); } else if (node instanceof IntegerLiteral) { - accept({ languageNode: node, severity: 'error', message: `i3-${node.value}` }); + accept({ + languageNode: node, + severity: "error", + message: `i3-${node.value}`, + }); } else { - accept({ languageNode: node, severity: 'error', message: `failure3-${node.constructor.name}` }); + accept({ + languageNode: node, + severity: "error", + message: `failure3-${node.constructor.name}`, + }); } }; }); - describe('Add validation rules with different language keys', () => { - test('String rule without any options', () => { + describe("Add validation rules with different language keys", () => { + test("String rule without any options", () => { addValidationRule(ruleString, {}); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String rule registered for String', () => { + test("String rule registered for String", () => { addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String rule registered for Integer => no validation issues', () => { + test("String rule registered for Integer => no validation issues", () => { addValidationRule(ruleString, { languageKey: IntegerLiteral.name }); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String+Integer rule without any options', () => { + test("String+Integer rule without any options", () => { addValidationRule(ruleStringInteger, {}); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); - expectValidationIssuesStrict(typir, booleanTrue, ['failure3-BooleanLiteral']); // generic message for everything else than strings and integers + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, booleanTrue, [ + "failure3-BooleanLiteral", + ]); // generic message for everything else than strings and integers }); - test('String+Integer rule registered for String', () => { - addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + test("String+Integer rule registered for String", () => { + addValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); expectValidationIssuesStrict(typir, integer123, []); // no messages for not-evaluated validations expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String+Integer rule registered for Integer', () => { - addValidationRule(ruleStringInteger, { languageKey: IntegerLiteral.name }); + test("String+Integer rule registered for Integer", () => { + addValidationRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String+Integer rule registered for String and Integer', () => { - addValidationRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name] }); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + test("String+Integer rule registered for String and Integer", () => { + addValidationRule(ruleStringInteger, { + languageKey: [StringLiteral.name, IntegerLiteral.name], + }); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String rule + Integer rule without any options', () => { - addValidationRule(ruleString, { }); - addValidationRule(ruleInteger, { }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i2-123']); + test("String rule + Integer rule without any options", () => { + addValidationRule(ruleString, {}); + addValidationRule(ruleInteger, {}); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i2-123"]); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String rule + Integer registered for their respective language keys', () => { + test("String rule + Integer registered for their respective language keys", () => { addValidationRule(ruleString, { languageKey: StringLiteral.name }); - addValidationRule(ruleInteger, { languageKey: IntegerLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i2-123']); + addValidationRule(ruleInteger, { + languageKey: IntegerLiteral.name, + }); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i2-123"]); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test('String rule + Integer + String+Integer rule without any options', () => { - addValidationRule(ruleString, { }); - addValidationRule(ruleInteger, { }); - addValidationRule(ruleStringInteger, { }); + test("String rule + Integer + String+Integer rule without any options", () => { + addValidationRule(ruleString, {}); + addValidationRule(ruleInteger, {}); + addValidationRule(ruleStringInteger, {}); assertNumberRules(3); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello', 's3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i2-123', 'i3-123']); - expectValidationIssuesStrict(typir, booleanTrue, ['failure3-BooleanLiteral']); + expectValidationIssuesStrict(typir, stringHello, [ + "s1-Hello", + "s3-Hello", + ]); + expectValidationIssuesStrict(typir, integer123, [ + "i2-123", + "i3-123", + ]); + expectValidationIssuesStrict(typir, booleanTrue, [ + "failure3-BooleanLiteral", + ]); }); - test('adding different rules', () => { + test("adding different rules", () => { assertNumberRules(0); - addValidationRule(ruleString, { }); + addValidationRule(ruleString, {}); assertNumberRules(1); - addValidationRule(ruleInteger, { }); + addValidationRule(ruleInteger, {}); assertNumberRules(2); - addValidationRule(ruleStringInteger, { }); + addValidationRule(ruleStringInteger, {}); assertNumberRules(3); }); test('Add the same rule for dedicated language keys and "undefined"', () => { - addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + addValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); expectValidationIssuesStrict(typir, integer123, []); addValidationRule(ruleStringInteger, { languageKey: undefined }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); }); - }); - - describe('Add the same rule multiple times', () => { - test('adding the same rule multiple times', () => { + describe("Add the same rule multiple times", () => { + test("adding the same rule multiple times", () => { assertNumberRules(0); - addValidationRule(ruleString, { }); + addValidationRule(ruleString, {}); assertNumberRules(1); - addValidationRule(ruleString, { }); + addValidationRule(ruleString, {}); assertNumberRules(1); - addValidationRule(ruleString, { }); + addValidationRule(ruleString, {}); assertNumberRules(1); }); - test('Adding the same rule for different language keys', () => { - addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); + test("Adding the same rule for different language keys", () => { + addValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); assertNumberRules(1); - addValidationRule(ruleStringInteger, { languageKey: IntegerLiteral.name }); + addValidationRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); expectValidationIssuesStrict(typir, booleanTrue, []); }); }); - describe('Remove validation rules with different language keys', () => { - - test('Removing a rule', () => { + describe("Remove validation rules with different language keys", () => { + test("Removing a rule", () => { expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); }); - test('Removing a rule (which was added twice)', () => { + test("Removing a rule (which was added twice)", () => { expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); }); - test('Removing a rule more often that it was added is OK', () => { - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + test("Removing a rule more often that it was added is OK", () => { + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); }); test('Remove the same rule for dedicated language keys and "undefined"', () => { addValidationRule(ruleStringInteger, { languageKey: undefined }); - removeValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); + removeValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); // it is still validated, since the rule is still registed for 'undefined' - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); // it is still validated, since the rule is still registed for 'undefined' + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); }); test('Remove the same rule for dedicated language keys and "undefined"', () => { - addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); + addValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); expectValidationIssuesStrict(typir, integer123, []); removeValidationRule(ruleStringInteger, { languageKey: undefined }); // the rule is removed for all language keys assertNumberRules(0); @@ -221,24 +300,25 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); }); - - describe('bound to type', () => { - + describe("bound to type", () => { test('remove bound rule automatically ("undefined" as language key)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); removeType(stringType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); }); - test('remove bound rule automatically (one dedicated language key: String)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name] }); + test("remove bound rule automatically (one dedicated language key: String)", () => { + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [StringLiteral.name], + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); expectValidationIssuesStrict(typir, integer123, []); removeType(stringType); assertNumberRules(0); @@ -246,22 +326,28 @@ describe('Tests the logic for registering rules (applied to state-less validatio expectValidationIssuesStrict(typir, integer123, []); }); - test('remove bound rule automatically (one dedicated language key: Integer)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [IntegerLiteral.name] }); + test("remove bound rule automatically (one dedicated language key: Integer)", () => { + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [IntegerLiteral.name], + }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, []); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); removeType(stringType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); }); - test('remove bound rule automatically (multiple dedicated language keys)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name, IntegerLiteral.name] }); + test("remove bound rule automatically (multiple dedicated language keys)", () => { + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [StringLiteral.name, IntegerLiteral.name], + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); assertNumberRules(1); removeType(stringType); // rule is removed for all language keys! assertNumberRules(0); @@ -269,15 +355,17 @@ describe('Tests the logic for registering rules (applied to state-less validatio expectValidationIssuesStrict(typir, integer123, []); }); - test('remove bound rule automatically, when the last type is removed', () => { - addValidationRule(ruleStringInteger, { boundToType: [stringType, integerType] }); + test("remove bound rule automatically, when the last type is removed", () => { + addValidationRule(ruleStringInteger, { + boundToType: [stringType, integerType], + }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); removeType(stringType); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); - expectValidationIssuesStrict(typir, integer123, ['i3-123']); + expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, integer123, ["i3-123"]); removeType(integerType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); @@ -285,25 +373,37 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); }); - function removeType(type: Type): void { typir.infrastructure.Graph.removeNode(type); } - function addValidationRule(rule: ValidationRule, options?: Partial) { + function addValidationRule( + rule: ValidationRule, + options?: Partial, + ) { typir.validation.Collector.addValidationRule(rule, options); } - function removeValidationRule(rule: ValidationRule, options?: Partial) { + function removeValidationRule( + rule: ValidationRule, + options?: Partial, + ) { typir.validation.Collector.removeValidationRule(rule, options); } function assertNumberRules(size: number): void { - const registry = (typir.validation.Collector as TestValidatorImpl).ruleRegistryFunctional; + const registry = (typir.validation.Collector as TestValidatorImpl) + .ruleRegistryFunctional; expect(registry.getNumberUniqueRules()).toBe(size); } }); class TestValidatorImpl extends DefaultValidationCollector { // make the public to access their details - override readonly ruleRegistryFunctional: RuleRegistry, TestLanguageNode>; - override readonly ruleRegistryLifecycle: RuleRegistry, TestLanguageNode>; + override readonly ruleRegistryFunctional: RuleRegistry< + ValidationRuleFunctional, + TestLanguageNode + >; + override readonly ruleRegistryLifecycle: RuleRegistry< + ValidationRuleLifecycle, + TestLanguageNode + >; } diff --git a/packages/typir/test/type-definitions.test.ts b/packages/typir/test/type-definitions.test.ts index 805f4747..6045442c 100644 --- a/packages/typir/test/type-definitions.test.ts +++ b/packages/typir/test/type-definitions.test.ts @@ -2,106 +2,195 @@ * Copyright 2024 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. -******************************************************************************/ + ******************************************************************************/ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, expect, test } from 'vitest'; -import { ClassKind } from '../src/kinds/class/class-kind.js'; -import { FunctionKind, NO_PARAMETER_NAME } from '../src/kinds/function/function-kind.js'; -import { PrimitiveKind } from '../src/kinds/primitive/primitive-kind.js'; -import { createTypirServices } from '../src/typir.js'; -import { MultiplicityKind } from '../src/kinds/multiplicity/multiplicity-kind.js'; -import { FixedParameterKind } from '../src/kinds/fixed-parameters/fixed-parameters-kind.js'; +import { describe, expect, test } from "vitest"; +import { ClassKind } from "../src/kinds/class/class-kind.js"; +import { + FunctionKind, + NO_PARAMETER_NAME, +} from "../src/kinds/function/function-kind.js"; +import { PrimitiveKind } from "../src/kinds/primitive/primitive-kind.js"; +import { createTypirServices } from "../src/typir.js"; +import { MultiplicityKind } from "../src/kinds/multiplicity/multiplicity-kind.js"; +import { FixedParameterKind } from "../src/kinds/fixed-parameters/fixed-parameters-kind.js"; -describe('Tests for Typir', () => { - test('Define some types', async () => { +describe("Tests for Typir", () => { + test("Define some types", async () => { // start the type system const typir = createTypirServices({ // customize some default factories for predefined types factory: { - Classes: (services) => new ClassKind(services, { typing: 'Structural', maximumNumberOfSuperClasses: 1, subtypeFieldChecking: 'SUB_TYPE' }), + Classes: (services) => + new ClassKind(services, { + typing: "Structural", + maximumNumberOfSuperClasses: 1, + subtypeFieldChecking: "SUB_TYPE", + }), }, }); // reuse predefined kinds - const multiplicityKind = new MultiplicityKind(typir, { symbolForUnlimited: '*' }); - const listKind = new FixedParameterKind(typir, 'List', { parameterSubtypeCheckingStrategy: 'EQUAL_TYPE' }, 'entry'); - const mapKind = new FixedParameterKind(typir, 'Map', { parameterSubtypeCheckingStrategy: 'EQUAL_TYPE' }, 'key', 'value'); + const multiplicityKind = new MultiplicityKind(typir, { + symbolForUnlimited: "*", + }); + const listKind = new FixedParameterKind( + typir, + "List", + { parameterSubtypeCheckingStrategy: "EQUAL_TYPE" }, + "entry", + ); + const mapKind = new FixedParameterKind( + typir, + "Map", + { parameterSubtypeCheckingStrategy: "EQUAL_TYPE" }, + "key", + "value", + ); // create some primitive types - const typeInt = typir.factory.Primitives.create({ primitiveName: 'Integer' }).finish(); - const typeString = typir.factory.Primitives.create({ primitiveName: 'String' }) - .inferenceRule({ filter: languageNode => typeof languageNode === 'string' }).finish(); // combine type definition with a dedicated inference rule for it - const typeBoolean = typir.factory.Primitives.create({ primitiveName: 'Boolean' }).finish(); + const typeInt = typir.factory.Primitives.create({ + primitiveName: "Integer", + }).finish(); + const typeString = typir.factory.Primitives.create({ + primitiveName: "String", + }) + .inferenceRule({ + filter: (languageNode) => typeof languageNode === "string", + }) + .finish(); // combine type definition with a dedicated inference rule for it + const typeBoolean = typir.factory.Primitives.create({ + primitiveName: "Boolean", + }).finish(); // create class type Person with 1 firstName and 1..2 lastNames and an age properties - const typeOneOrTwoStrings = multiplicityKind.createMultiplicityType({ constrainedType: typeString, lowerBound: 1, upperBound: 2 }); + const typeOneOrTwoStrings = multiplicityKind.createMultiplicityType({ + constrainedType: typeString, + lowerBound: 1, + upperBound: 2, + }); const typePerson = typir.factory.Classes.create({ - className: 'Person', + className: "Person", fields: [ - { name: 'firstName', type: typeString }, - { name: 'lastName', type: typeOneOrTwoStrings }, - { name: 'age', type: typeInt } + { name: "firstName", type: typeString }, + { name: "lastName", type: typeOneOrTwoStrings }, + { name: "age", type: typeInt }, ], methods: [], }).finish(); console.log(typePerson.getTypeFinal()!.getUserRepresentation()); const typeStudent = typir.factory.Classes.create({ - className: 'Student', + className: "Student", superClasses: typePerson, // a Student is a special Person - fields: [ - { name: 'studentNumber', type: typeInt } - ], - methods: [] + fields: [{ name: "studentNumber", type: typeInt }], + methods: [], }).finish(); // create some more types - const typeListInt = listKind.createFixedParameterType({ parameterTypes: typeInt }); - const typeListString = listKind.createFixedParameterType({ parameterTypes: typeString }); + const typeListInt = listKind.createFixedParameterType({ + parameterTypes: typeInt, + }); + const typeListString = listKind.createFixedParameterType({ + parameterTypes: typeString, + }); // const typeMapStringPerson = mapKind.createFixedParameterType({ parameterTypes: [typeString, typePerson] }); const typeFunctionStringLength = typir.factory.Functions.create({ - functionName: 'length', + functionName: "length", outputParameter: { name: NO_PARAMETER_NAME, type: typeInt }, - inputParameters: [{ name: 'value', type: typeString }] + inputParameters: [{ name: "value", type: typeString }], }).finish(); // binary operators on Integers - const opAdd = typir.factory.Operators.createBinary({ name: '+', signature: { left: typeInt, right: typeInt, return: typeInt } }).finish(); - const opMinus = typir.factory.Operators.createBinary({ name: '-', signature: { left: typeInt, right: typeInt, return: typeInt } }).finish(); - const opLess = typir.factory.Operators.createBinary({ name: '<', signature: { left: typeInt, right: typeInt, return: typeBoolean } }).finish(); - const opEqualInt = typir.factory.Operators.createBinary({ name: '==', signature: { left: typeInt, right: typeInt, return: typeBoolean } }) + const opAdd = typir.factory.Operators.createBinary({ + name: "+", + signature: { left: typeInt, right: typeInt, return: typeInt }, + }).finish(); + const opMinus = typir.factory.Operators.createBinary({ + name: "-", + signature: { left: typeInt, right: typeInt, return: typeInt }, + }).finish(); + const opLess = typir.factory.Operators.createBinary({ + name: "<", + signature: { left: typeInt, right: typeInt, return: typeBoolean }, + }).finish(); + const opEqualInt = typir.factory.Operators.createBinary({ + name: "==", + signature: { left: typeInt, right: typeInt, return: typeBoolean }, + }) .inferenceRule({ - filter: (languageNode): languageNode is string => typeof languageNode === 'string', - matching: languageNode => languageNode.includes('=='), - operands: languageNode => [] - }).finish(); + filter: (languageNode): languageNode is string => + typeof languageNode === "string", + matching: (languageNode) => languageNode.includes("=="), + operands: (languageNode) => [], + }) + .finish(); // binary operators on Booleans - const opEqualBool = typir.factory.Operators.createBinary({ name: '==', signature: { left: typeBoolean, right: typeBoolean, return: typeBoolean } }).finish(); - const opAnd = typir.factory.Operators.createBinary({ name: '&&', signature: { left: typeBoolean, right: typeBoolean, return: typeBoolean } }).finish(); + const opEqualBool = typir.factory.Operators.createBinary({ + name: "==", + signature: { + left: typeBoolean, + right: typeBoolean, + return: typeBoolean, + }, + }).finish(); + const opAnd = typir.factory.Operators.createBinary({ + name: "&&", + signature: { + left: typeBoolean, + right: typeBoolean, + return: typeBoolean, + }, + }).finish(); // unary operators - const opNotBool = typir.factory.Operators.createUnary({ name: '!', signature: { operand: typeBoolean, return: typeBoolean } }) + const opNotBool = typir.factory.Operators.createUnary({ + name: "!", + signature: { operand: typeBoolean, return: typeBoolean }, + }) .inferenceRule({ - filter: (languageNode): languageNode is string => typeof languageNode === 'string', - matching: languageNode => languageNode.includes('NOT'), - operand: languageNode => [] - }).finish(); + filter: (languageNode): languageNode is string => + typeof languageNode === "string", + matching: (languageNode) => languageNode.includes("NOT"), + operand: (languageNode) => [], + }) + .finish(); // ternary operator - const opTernaryIf = typir.factory.Operators.createTernary({ name: 'if', signature: { first: typeBoolean, second: typeInt, third: typeInt, return: typeInt } }).finish(); + const opTernaryIf = typir.factory.Operators.createTernary({ + name: "if", + signature: { + first: typeBoolean, + second: typeInt, + third: typeInt, + return: typeInt, + }, + }).finish(); // automated conversion from int to string - typir.Conversion.markAsConvertible(typeInt, typeString, 'EXPLICIT'); + typir.Conversion.markAsConvertible(typeInt, typeString, "EXPLICIT"); // single relationships are possible as well - typir.Conversion.markAsConvertible(typeInt, typeString, 'IMPLICIT_EXPLICIT'); + typir.Conversion.markAsConvertible( + typeInt, + typeString, + "IMPLICIT_EXPLICIT", + ); // is assignable? // primitives expect(typir.Assignability.isAssignable(typeInt, typeInt)).toBe(true); - expect(typir.Assignability.isAssignable(typeInt, typeString)).toBe(true); - expect(typir.Assignability.isAssignable(typeString, typeInt)).not.toBe(true); + expect(typir.Assignability.isAssignable(typeInt, typeString)).toBe( + true, + ); + expect(typir.Assignability.isAssignable(typeString, typeInt)).not.toBe( + true, + ); // List, Map // expect(typir.assignability.isAssignable(typeListInt, typeMapStringPerson)).not.toBe(true); - expect(typir.Assignability.isAssignable(typeListInt, typeListString)).not.toBe(true); - expect(typir.Assignability.isAssignable(typeListInt, typeListInt)).toBe(true); + expect( + typir.Assignability.isAssignable(typeListInt, typeListString), + ).not.toBe(true); + expect(typir.Assignability.isAssignable(typeListInt, typeListInt)).toBe( + true, + ); // classes // expect(typir.assignability.isAssignable(typeStudent, typePerson)).toBe(true); // const assignConflicts = typir.assignability.getAssignabilityProblem(typePerson, typeStudent); diff --git a/packages/typir/test/utils/test-utils.test.ts b/packages/typir/test/utils/test-utils.test.ts index 45c349f1..e477173d 100644 --- a/packages/typir/test/utils/test-utils.test.ts +++ b/packages/typir/test/utils/test-utils.test.ts @@ -4,10 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, test } from 'vitest'; -import { integer123, IntegerLiteral, StatementBlock, TestExpressionNode, TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; -import { TypirServices } from '../../src/typir.js'; -import { createTypirServicesForTesting, expectValidationIssues, expectValidationIssuesAbsent, expectValidationIssuesNone, expectValidationIssuesStrict } from '../../src/utils/test-utils.js'; +import { beforeAll, describe, test } from "vitest"; +import { + integer123, + IntegerLiteral, + StatementBlock, + TestExpressionNode, + TestLanguageNode, +} from "../../src/test/predefined-language-nodes.js"; +import { TypirServices } from "../../src/typir.js"; +import { + createTypirServicesForTesting, + expectValidationIssues, + expectValidationIssuesAbsent, + expectValidationIssuesNone, + expectValidationIssuesStrict, +} from "../../src/utils/test-utils.js"; describe('Test cases for the "expectValidationIssues*(...)" test utilities', () => { let typir: TypirServices; @@ -16,64 +28,102 @@ describe('Test cases for the "expectValidationIssues*(...)" test utilities', () typir = createTypirServicesForTesting(); typir.validation.Collector.addValidationRule((node, accept) => { if (node instanceof TestExpressionNode) { - accept({ languageNode: node, severity: 'error', message: 'found Expression'}); + accept({ + languageNode: node, + severity: "error", + message: "found Expression", + }); } if (node instanceof IntegerLiteral) { - accept({ languageNode: node, severity: 'error', message: 'found Integer literal'}); + accept({ + languageNode: node, + severity: "error", + message: "found Integer literal", + }); } }); }); - test('some issues (some of the actual issues are expected)', () => { - expectValidationIssues(typir, integer123, ['found Integer literal']); // "found Expression" is ignored here + test("some issues (some of the actual issues are expected)", () => { + expectValidationIssues(typir, integer123, ["found Integer literal"]); // "found Expression" is ignored here }); - test('some issues (all of the actual issues are expected)', () => { - expectValidationIssues(typir, integer123, ['found Integer literal', 'found Expression']); + test("some issues (all of the actual issues are expected)", () => { + expectValidationIssues(typir, integer123, [ + "found Integer literal", + "found Expression", + ]); }); - test('some issues (none of the actual issues are expected)', () => { + test("some issues (none of the actual issues are expected)", () => { expectValidationIssues(typir, integer123, []); }); - test.fails('some issues (fails, since an issue is expected, but does not occur)', () => { - expectValidationIssues(typir, integer123, ['found Integer literal', 'found WhatEverNode']); - }); + test.fails( + "some issues (fails, since an issue is expected, but does not occur)", + () => { + expectValidationIssues(typir, integer123, [ + "found Integer literal", + "found WhatEverNode", + ]); + }, + ); - test('strict (all of the actual issues are expected)', () => { - expectValidationIssuesStrict(typir, integer123, ['found Integer literal', 'found Expression']); - }); - test('strict (all of the actual issues are expected: errors)', () => { - expectValidationIssuesStrict(typir, integer123, { severity: 'error' }, ['found Integer literal', 'found Expression']); - }); - test('strict (all of the actual issues are expected: warnings)', () => { - expectValidationIssuesStrict(typir, integer123, { severity: 'warning' }, []); - }); - test.fails('strict (fails: too less)', () => { - expectValidationIssuesStrict(typir, integer123, ['found Integer literal']); - }); - test.fails('strict (fails: too much)', () => { - expectValidationIssuesStrict(typir, integer123, ['found Integer literal', 'found Expression', 'found WhatEverNode']); + test("strict (all of the actual issues are expected)", () => { + expectValidationIssuesStrict(typir, integer123, [ + "found Integer literal", + "found Expression", + ]); + }); + test("strict (all of the actual issues are expected: errors)", () => { + expectValidationIssuesStrict(typir, integer123, { severity: "error" }, [ + "found Integer literal", + "found Expression", + ]); + }); + test("strict (all of the actual issues are expected: warnings)", () => { + expectValidationIssuesStrict( + typir, + integer123, + { severity: "warning" }, + [], + ); + }); + test.fails("strict (fails: too less)", () => { + expectValidationIssuesStrict(typir, integer123, [ + "found Integer literal", + ]); + }); + test.fails("strict (fails: too much)", () => { + expectValidationIssuesStrict(typir, integer123, [ + "found Integer literal", + "found Expression", + "found WhatEverNode", + ]); }); - test('absent (only a absent issue)', () => { - expectValidationIssuesAbsent(typir, integer123, ['found WhatEverNode']); + test("absent (only a absent issue)", () => { + expectValidationIssuesAbsent(typir, integer123, ["found WhatEverNode"]); }); - test.fails('absent (fails, since the given issue occurs)', () => { - expectValidationIssuesAbsent(typir, integer123, ['found Expression']); + test.fails("absent (fails, since the given issue occurs)", () => { + expectValidationIssuesAbsent(typir, integer123, ["found Expression"]); }); - test('absent (the specified issue occurs as error, not as warning)', () => { - expectValidationIssuesAbsent(typir, integer123, { severity: 'warning' }, ['found Expression']); + test("absent (the specified issue occurs as error, not as warning)", () => { + expectValidationIssuesAbsent( + typir, + integer123, + { severity: "warning" }, + ["found Expression"], + ); }); - test('absent (works even for an empty array)', () => { + test("absent (works even for an empty array)", () => { expectValidationIssuesAbsent(typir, integer123, []); }); - test('none (at all)', () => { + test("none (at all)", () => { expectValidationIssuesNone(typir, new StatementBlock([])); }); - test('none warnings', () => { - expectValidationIssuesNone(typir, integer123, { severity: 'warning' }); + test("none warnings", () => { + expectValidationIssuesNone(typir, integer123, { severity: "warning" }); }); - test.fails('none errors fails, since there are error issues', () => { - expectValidationIssuesNone(typir, integer123, { severity: 'error' }); + test.fails("none errors fails, since there are error issues", () => { + expectValidationIssuesNone(typir, integer123, { severity: "error" }); }); - }); diff --git a/scripts/update-version.js b/scripts/update-version.js index 2bd0acd9..78d1cd76 100644 --- a/scripts/update-version.js +++ b/scripts/update-version.js @@ -18,7 +18,7 @@ async function replaceAll(project, pkg, versions) { const path = getPath(project, pkg); let content = await fs.readFile(path, 'utf-8'); versions.forEach(([project, version]) => { - const regex = new RegExp("(?<=\"" + project + "\": \"[~\\^]?)\\d+\\.\\d+\\.\\d+", "g"); + const regex = new RegExp('(?<="' + project + '": "[~\\^]?)\\d+\\.\\d+\\.\\d+', 'g'); content = content.replace(regex, version); }); await fs.writeFile(path, content); diff --git a/vite.config.ts b/vite.config.ts index feaea844..1a68847c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,18 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { defineConfig } from 'vitest/config'; +import { defineConfig } from "vitest/config"; export default defineConfig({ test: { coverage: { - provider: 'c8', - reporter: ['text', 'html'], - include: ['packages/typir/src'], - exclude: ['**/generated'], + provider: "c8", + reporter: ["text", "html"], + include: ["packages/typir/src"], + exclude: ["**/generated"], }, deps: { - interopDefault: true - } - } + interopDefault: true, + }, + }, }); From 6196814a4eaa9d2b65b9267f7264f353bf66bea0 Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 10:54:10 +0200 Subject: [PATCH 2/8] Fix old packages --- eslint.config.mjs | 11 +- examples/lox/src/cli/main.ts | 2 +- examples/ox/src/cli/main.ts | 2 +- package-lock.json | 361 ++++++++++++++++++----------------- package.json | 3 +- packages/typir/src/typir.ts | 2 +- 6 files changed, 201 insertions(+), 180 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index f25ee980..243fccf5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,6 +22,11 @@ export default defineConfig([ "**/*.js", "**/*.cjs", "packages/typir/lib/**", + "packages/typir-langium/lib/**", + "examples/lox/src/language/generated/**", + "examples/ox/src/language/generated/**", + "examples/lox/out/**", + "examples/ox/out/**", ], }, { @@ -83,7 +88,7 @@ export default defineConfig([ quotes: [ 2, - "single", + "double", { avoidEscape: true, }, @@ -104,9 +109,8 @@ export default defineConfig([ }, ], - "@typescript-eslint/ban-types": "error", + "@typescript-eslint/no-empty-object-type": "off", "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/indent": "error", "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-non-null-assertion": "off", @@ -123,7 +127,6 @@ export default defineConfig([ "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-namespace-keyword": "error", "@typescript-eslint/triple-slash-reference": "error", - "@typescript-eslint/type-annotation-spacing": "error", }, }, ]); diff --git a/examples/lox/src/cli/main.ts b/examples/lox/src/cli/main.ts index 07a34fe7..995f36d7 100644 --- a/examples/lox/src/cli/main.ts +++ b/examples/lox/src/cli/main.ts @@ -34,7 +34,7 @@ export type GenerateOptions = { destination?: string; }; -export default function (): void { +export default function(): void { const program = new Command(); program.version(JSON.parse(packageContent).version); diff --git a/examples/ox/src/cli/main.ts b/examples/ox/src/cli/main.ts index 2f6c83ad..ef5feb11 100644 --- a/examples/ox/src/cli/main.ts +++ b/examples/ox/src/cli/main.ts @@ -34,7 +34,7 @@ export type GenerateOptions = { destination?: string; }; -export default function (): void { +export default function(): void { const program = new Command(); program.version(JSON.parse(packageContent).version); diff --git a/package-lock.json b/package-lock.json index 57456f1c..8ed62d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ ], "devDependencies": { "@types/node": "~18.19.55", - "@typescript-eslint/eslint-plugin": "~7.18.0", - "@typescript-eslint/parser": "~7.18.0", + "@typescript-eslint/eslint-plugin": "^8.33.0", "@vitest/ui": "~2.1.2", "concurrently": "~9.0.1", "editorconfig": "~2.0.0", @@ -547,16 +546,20 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } @@ -746,6 +749,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -759,6 +763,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -768,6 +773,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1025,141 +1031,157 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", + "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/type-utils": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.33.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", + "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", + "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@typescript-eslint/tsconfig-utils": "^8.33.0", + "@typescript-eslint/types": "^8.33.0", + "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", + "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", + "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "eslint": "^8.56.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", + "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1167,31 +1189,32 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", + "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/project-service": "8.33.0", + "@typescript-eslint/tsconfig-utils": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -1199,6 +1222,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1208,6 +1232,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1218,23 +1243,61 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", + "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.33.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitest/expect": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", @@ -1434,15 +1497,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1472,6 +1526,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1720,18 +1775,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/editorconfig": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-2.0.0.tgz", @@ -2074,16 +2117,17 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -2103,10 +2147,11 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -2135,6 +2180,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2256,6 +2302,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2276,26 +2323,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2306,7 +2333,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -2437,6 +2465,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2636,6 +2665,7 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -2645,6 +2675,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2815,15 +2846,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -2850,6 +2872,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2938,7 +2961,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/railroad-diagrams": { "version": "1.0.0", @@ -2995,10 +3019,11 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3058,6 +3083,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -3165,15 +3191,6 @@ "node": ">= 10" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3341,6 +3358,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3367,15 +3385,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { diff --git a/package.json b/package.json index 37b7934d..29cdb573 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ }, "devDependencies": { "@types/node": "~18.19.55", - "@typescript-eslint/eslint-plugin": "~7.18.0", - "@typescript-eslint/parser": "~7.18.0", + "@typescript-eslint/eslint-plugin": "^8.33.0", "@vitest/ui": "~2.1.2", "concurrently": "~9.0.1", "editorconfig": "~2.0.0", diff --git a/packages/typir/src/typir.ts b/packages/typir/src/typir.ts index e4a3439a..90fbc6e9 100644 --- a/packages/typir/src/typir.ts +++ b/packages/typir/src/typir.ts @@ -221,7 +221,7 @@ export function createTypirServices( * any methods. If it does, it's one of our services and therefore should not be partialized. * Copied from Langium. */ -//eslint-disable-next-line @typescript-eslint/ban-types +//eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export type DeepPartial = T[keyof T] extends Function ? T : { From 14552be4b836c4eeac2d28934b1086fa92944d9e Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 10:56:40 +0200 Subject: [PATCH 3/8] Add lint and format actions --- .github/workflows/actions.yml | 44 ---------------------- .github/workflows/ci.yml | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/actions.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml deleted file mode 100644 index d27dea2f..00000000 --- a/.github/workflows/actions.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Build - -on: - push: - branches: - - '**' - tags-ignore: - - '**' - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - build: - name: typir-build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Volta - uses: volta-cli/action@v4 - - - name: Install - shell: bash - run: | - npm ci - - - name: Build - shell: bash - run: | - npm run build - -# - name: Lint -# shell: bash -# run: | -# npm run lint - - - name: Test - shell: bash - run: | - npm run test:run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..164b1a87 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + branches: + - "**" + tags-ignore: + - "**" + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + format: + name: typir-format + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Volta + uses: volta-cli/action@v4 + - name: Install + shell: bash + run: | + npm ci + - name: Format + shell: bash + run: | + npm run pretty:check + + lint: + name: typir-lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Volta + uses: volta-cli/action@v4 + - name: Install + shell: bash + run: | + npm ci + - name: Format + shell: bash + run: | + npm run lint + + build: + name: typir-build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Volta + uses: volta-cli/action@v4 + - name: Install + shell: bash + run: | + npm ci + - name: Build + shell: bash + run: | + npm run build + - name: Test + shell: bash + run: | + npm run test:run From 7c1659f6aa642fe5806a79a877e4e8766e2b5d68 Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 10:59:25 +0200 Subject: [PATCH 4/8] Fix duplicated workflow --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 164b1a87..c0a92a4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,7 @@ name: CI on: push: - branches: - - "**" + branches: ["main"] tags-ignore: - "**" pull_request: From f26d5b4b19fe5366a67f72874553b3eeafc95216 Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 11:13:30 +0200 Subject: [PATCH 5/8] Add prettier file --- .prettierrc.json | 12 ++++++++++++ eslint.config.mjs | 9 --------- examples/lox/src/cli/main.ts | 2 +- examples/ox/src/cli/main.ts | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 .prettierrc.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..ec7c953b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,12 @@ +{ + "semi": true, + "overrides": [ + { + "files": "*.ts", + "options": { + "semi": true, + "tabWidth": 4 + } + } + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 243fccf5..71a3e131 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -75,15 +75,6 @@ export default defineConfig([ "no-unsafe-finally": "error", "no-var": "error", - "space-before-function-paren": [ - "error", - { - anonymous: "never", - asyncArrow: "always", - named: "never", - }, - ], - semi: [2, "always"], quotes: [ diff --git a/examples/lox/src/cli/main.ts b/examples/lox/src/cli/main.ts index 995f36d7..07a34fe7 100644 --- a/examples/lox/src/cli/main.ts +++ b/examples/lox/src/cli/main.ts @@ -34,7 +34,7 @@ export type GenerateOptions = { destination?: string; }; -export default function(): void { +export default function (): void { const program = new Command(); program.version(JSON.parse(packageContent).version); diff --git a/examples/ox/src/cli/main.ts b/examples/ox/src/cli/main.ts index ef5feb11..2f6c83ad 100644 --- a/examples/ox/src/cli/main.ts +++ b/examples/ox/src/cli/main.ts @@ -34,7 +34,7 @@ export type GenerateOptions = { destination?: string; }; -export default function(): void { +export default function (): void { const program = new Command(); program.version(JSON.parse(packageContent).version); From bbf13e1525576e5ebb68578b244dea8bd4f89eee Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 11:14:31 +0200 Subject: [PATCH 6/8] Rename ci jobs --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0a92a4d..4d9b652a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ on: jobs: format: - name: typir-format runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -30,7 +29,6 @@ jobs: npm run pretty:check lint: - name: typir-lint runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -48,7 +46,6 @@ jobs: npm run lint build: - name: typir-build runs-on: ubuntu-latest timeout-minutes: 10 steps: From ca34526386cc1c904e0a432d233a2900e64b939d Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 11:15:50 +0200 Subject: [PATCH 7/8] Remove commented out code --- eslint.config.mjs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 71a3e131..f0555dc6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -87,10 +87,6 @@ export default defineConfig([ "use-isnan": "error", - // "header/header": [2, "block", { - // pattern: "MIT License|DO NOT EDIT MANUALLY!", - // }], - "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": [ From 7cbba5abe5ee787d6b45ba9fdb3e5af4aee82368 Mon Sep 17 00:00:00 2001 From: Didrik Munther Date: Wed, 28 May 2025 12:24:39 +0200 Subject: [PATCH 8/8] Remove prettier and add langium eslint rules with stylistic --- .github/workflows/ci.yml | 19 +- .prettierrc.json | 12 - eslint.config.mjs | 324 ++- examples/lox/src/cli/cli-util.ts | 18 +- examples/lox/src/cli/main.ts | 34 +- examples/lox/src/extension/main.ts | 22 +- examples/lox/src/language/lox-linker.ts | 18 +- examples/lox/src/language/lox-module.ts | 30 +- examples/lox/src/language/lox-scope.ts | 32 +- .../lox/src/language/lox-type-checking.ts | 96 +- examples/lox/src/language/lox-utils.ts | 2 +- examples/lox/src/language/lox-validator.ts | 17 +- examples/lox/src/language/main.ts | 8 +- examples/lox/syntaxes/lox.monarch.ts | 106 +- .../test/lox-type-checking-classes.test.ts | 98 +- .../lox/test/lox-type-checking-cycles.test.ts | 206 +- .../test/lox-type-checking-functions.test.ts | 70 +- .../lox/test/lox-type-checking-method.test.ts | 61 +- .../test/lox-type-checking-operators.test.ts | 76 +- .../test/lox-type-checking-statements.test.ts | 70 +- examples/lox/test/lox-type-checking-utils.ts | 61 +- examples/ox/src/cli/cli-util.ts | 18 +- examples/ox/src/cli/main.ts | 34 +- examples/ox/src/extension/main.ts | 20 +- examples/ox/src/language/main.ts | 8 +- examples/ox/src/language/ox-module.ts | 23 +- examples/ox/src/language/ox-type-checking.ts | 64 +- examples/ox/src/language/ox-validator.ts | 34 +- .../test/ox-type-checking-functions.test.ts | 28 +- .../test/ox-type-checking-operators.test.ts | 70 +- .../test/ox-type-checking-statements.test.ts | 56 +- examples/ox/test/ox-type-checking-utils.ts | 52 +- package-lock.json | 2032 ++++++++++++++++- package.json | 7 +- .../src/features/langium-caching.ts | 15 +- .../src/features/langium-inference.ts | 17 +- .../src/features/langium-language.ts | 10 +- .../src/features/langium-printing.ts | 7 +- .../src/features/langium-type-creator.ts | 22 +- .../src/features/langium-validation.ts | 18 +- packages/typir-langium/src/index.ts | 16 +- packages/typir-langium/src/typir-langium.ts | 41 +- .../src/utils/typir-langium-utils.ts | 8 +- packages/typir/src/graph/graph-algorithms.ts | 26 +- packages/typir/src/graph/type-edge.ts | 8 +- packages/typir/src/graph/type-graph.ts | 18 +- packages/typir/src/graph/type-node.ts | 55 +- packages/typir/src/index-test.ts | 2 +- packages/typir/src/index.ts | 94 +- .../src/initialization/type-initializer.ts | 6 +- .../src/initialization/type-reference.ts | 28 +- .../typir/src/initialization/type-selector.ts | 21 +- .../typir/src/initialization/type-waiting.ts | 20 +- .../typir/src/kinds/bottom/bottom-kind.ts | 27 +- .../typir/src/kinds/bottom/bottom-type.ts | 13 +- .../src/kinds/class/class-initializer.ts | 39 +- packages/typir/src/kinds/class/class-kind.ts | 82 +- packages/typir/src/kinds/class/class-type.ts | 47 +- .../typir/src/kinds/class/class-validation.ts | 22 +- .../typir/src/kinds/class/top-class-kind.ts | 23 +- .../typir/src/kinds/class/top-class-type.ts | 19 +- .../fixed-parameters/fixed-parameters-kind.ts | 21 +- .../fixed-parameters/fixed-parameters-type.ts | 22 +- .../kinds/function/function-inference-call.ts | 31 +- .../function/function-inference-overloaded.ts | 20 +- .../kinds/function/function-initializer.ts | 24 +- .../typir/src/kinds/function/function-kind.ts | 77 +- .../kinds/function/function-overloading.ts | 23 +- .../typir/src/kinds/function/function-type.ts | 42 +- .../function/function-validation-calls.ts | 44 +- .../function/function-validation-unique.ts | 13 +- packages/typir/src/kinds/kind.ts | 4 +- .../kinds/multiplicity/multiplicity-kind.ts | 17 +- .../kinds/multiplicity/multiplicity-type.ts | 24 +- .../src/kinds/primitive/primitive-kind.ts | 25 +- .../src/kinds/primitive/primitive-type.ts | 17 +- packages/typir/src/kinds/top/top-kind.ts | 27 +- packages/typir/src/kinds/top/top-type.ts | 13 +- packages/typir/src/services/assignability.ts | 36 +- packages/typir/src/services/caching.ts | 60 +- packages/typir/src/services/conversion.ts | 61 +- packages/typir/src/services/equality.ts | 46 +- packages/typir/src/services/inference.ts | 47 +- packages/typir/src/services/kind-registry.ts | 14 +- packages/typir/src/services/language.ts | 2 +- packages/typir/src/services/operator.ts | 70 +- packages/typir/src/services/printing.ts | 46 +- packages/typir/src/services/subtype.ts | 33 +- packages/typir/src/services/validation.ts | 63 +- .../src/test/predefined-language-nodes.ts | 26 +- packages/typir/src/typir.ts | 113 +- .../typir/src/utils/dependency-injection.ts | 12 +- packages/typir/src/utils/rule-registration.ts | 12 +- packages/typir/src/utils/test-utils.ts | 31 +- packages/typir/src/utils/utils-definitions.ts | 27 +- .../typir/src/utils/utils-type-comparison.ts | 51 +- packages/typir/src/utils/utils.ts | 6 +- packages/typir/test/api-example.test.ts | 40 +- packages/typir/test/kinds/class/class.test.ts | 60 +- .../function/operator-inference-call.test.ts | 78 +- .../function/operator-overloaded.test.ts | 140 +- ...operator-validation-call-arguments.test.ts | 68 +- .../test/kinds/primitive/primitive.test.ts | 54 +- .../typir/test/services/conversion.test.ts | 18 +- .../test/services/inference-registry.test.ts | 63 +- .../test/services/validation-registry.test.ts | 164 +- packages/typir/test/type-definitions.test.ts | 90 +- packages/typir/test/utils/test-utils.test.ts | 92 +- vite.config.ts | 10 +- 109 files changed, 4280 insertions(+), 2327 deletions(-) delete mode 100644 .prettierrc.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d9b652a..739446ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,24 +11,7 @@ on: workflow_dispatch: jobs: - format: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Volta - uses: volta-cli/action@v4 - - name: Install - shell: bash - run: | - npm ci - - name: Format - shell: bash - run: | - npm run pretty:check - - lint: + format-and-lint: runs-on: ubuntu-latest timeout-minutes: 10 steps: diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index ec7c953b..00000000 --- a/.prettierrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "semi": true, - "overrides": [ - { - "files": "*.ts", - "options": { - "semi": true, - "tabWidth": 4 - } - } - ] -} diff --git a/eslint.config.mjs b/eslint.config.mjs index f0555dc6..d7909d31 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,11 +1,17 @@ -import { defineConfig } from "eslint/config"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import header from "eslint-plugin-header"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +/*[object Object]*/ +import { defineConfig } from 'eslint/config'; +import tsParser from '@typescript-eslint/parser'; +import globals from 'globals'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; + +import pluginTypescriptEslint from '@typescript-eslint/eslint-plugin'; +import pluginImport from 'eslint-plugin-import'; +import pluginUnusedImports from 'eslint-plugin-unused-imports'; +import pluginHeader from 'eslint-plugin-header'; +import pluginStylistic from '@stylistic/eslint-plugin'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -15,105 +21,279 @@ const compat = new FlatCompat({ allConfig: js.configs.all, }); +// Workaround, see https://github.com/Stuk/eslint-plugin-header/issues/57#issuecomment-2378485611 +pluginHeader.rules.header.meta.schema = false; + export default defineConfig([ { ignores: [ - "**/\\{node_modules,lib,bin}", - "**/*.js", - "**/*.cjs", - "packages/typir/lib/**", - "packages/typir-langium/lib/**", - "examples/lox/src/language/generated/**", - "examples/ox/src/language/generated/**", - "examples/lox/out/**", - "examples/ox/out/**", + '**/\\{node_modules,lib,bin}', + '**/*.js', + '**/*.cjs', + 'packages/typir/lib/**', + 'packages/typir-langium/lib/**', + '**/out/**/*', + '**/language/generated/**/*', ], }, { - files: ["**/*.ts"], + files: [ + 'packages/*/src/**/*.ts', + 'packages/*/test/**/*.ts', + 'examples/*/src/**/*.ts', + 'examples/*/test/**/*.ts', + 'examples/*/syntaxes/**/*.ts', + ], extends: compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/recommended", + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', ), plugins: { - "@typescript-eslint": typescriptEslint, - header, + '@typescript-eslint': pluginTypescriptEslint, + import: pluginImport, + 'unused-imports': pluginUnusedImports, + pluginHeader, + '@stylistic': pluginStylistic, }, languageOptions: { + globals: { + ...globals.node, + ...globals.browser, + }, parser: tsParser, ecmaVersion: 2017, - sourceType: "module", + sourceType: 'module', }, rules: { - "arrow-parens": ["off", "as-needed"], - "constructor-super": "error", - "dot-notation": "error", - eqeqeq: "error", - "guard-for-in": "error", - "new-parens": "error", - "no-bitwise": "error", - "no-caller": "error", - "no-cond-assign": "error", - "no-debugger": "error", - "no-eval": "error", - "no-inner-declarations": "off", - "no-labels": "error", - - "no-multiple-empty-lines": [ - "error", - { - max: 3, - }, - ], + // "arrow-parens": ["off", "as-needed"], + // "constructor-super": "error", + // "dot-notation": "error", + // eqeqeq: "error", + // "guard-for-in": "error", + // "new-parens": "error", + // "no-bitwise": "error", + // "no-caller": "error", + // "no-cond-assign": "error", + // "no-debugger": "error", + // "no-eval": "error", + // "no-inner-declarations": "off", + // "no-labels": "error", + + // "no-multiple-empty-lines": [ + // "error", + // { + // max: 3, + // }, + // ], + + // "no-new-wrappers": "error", + // "no-throw-literal": "error", + // "no-trailing-spaces": "error", + // "no-unsafe-finally": "error", + // "no-var": "error", - "no-new-wrappers": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-unsafe-finally": "error", - "no-var": "error", + // semi: [2, "always"], - semi: [2, "always"], + // quotes: [ + // 2, + // "double", + // { + // avoidEscape: true, + // }, + // ], + // "use-isnan": "error", + + // "@typescript-eslint/adjacent-overload-signatures": "error", + + // "@typescript-eslint/array-type": [ + // "error", + // { + // default: "array-simple", + // }, + // ], + + // "@typescript-eslint/no-empty-object-type": "off", + // "@typescript-eslint/no-inferrable-types": "off", + // "@typescript-eslint/no-misused-new": "error", + // "@typescript-eslint/no-namespace": "off", + // "@typescript-eslint/no-non-null-assertion": "off", + // "@typescript-eslint/parameter-properties": "error", + + // "@typescript-eslint/no-unused-vars": [ + // "error", + // { + // argsIgnorePattern: "^_", + // }, + // ], + + // "@typescript-eslint/no-var-requires": "error", + // "@typescript-eslint/prefer-for-of": "error", + // "@typescript-eslint/prefer-namespace-keyword": "error", + // "@typescript-eslint/triple-slash-reference": "error", + + // do not force arrow function parentheses + 'arrow-parens': ['off', 'as-needed'], + // checks the correct use of super() in sub-classes + 'constructor-super': 'error', + // obj.a instead of obj['a'] when possible + 'dot-notation': 'error', + // ban '==', don't use 'smart' option! + eqeqeq: 'error', + // needs obj.hasOwnProperty(key) checks + 'guard-for-in': 'error', + // new Error() instead of new Error + 'new-parens': 'error', + // bitwise operators &, | can be confused with &&, || + 'no-bitwise': 'error', + // ECMAScript deprecated arguments.caller and arguments.callee + 'no-caller': 'error', + // assignments if (a = '1') are error-prone + 'no-cond-assign': 'error', + // disallow debugger; statements + 'no-debugger': 'error', + // eval is considered unsafe + 'no-eval': 'error', + // we need to have 'namespace' functions when using TS 'export =' + 'no-inner-declarations': 'off', + // GOTO is only used in BASIC ;) + 'no-labels': 'error', + // two or more empty lines need to be fused to one + 'no-multiple-empty-lines': [ + 'error', + { + max: 1, + }, + ], + // there is no reason to wrap primitve values + 'no-new-wrappers': 'error', + // only throw Error but no objects {} + 'no-throw-literal': 'error', + // trim end of lines + 'no-trailing-spaces': 'error', + // safe try/catch/finally behavior + 'no-unsafe-finally': 'error', + // use const and let instead of var + 'no-var': 'error', + // space in function decl: f() vs async () => {} + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + asyncArrow: 'always', + named: 'never', + }, + ], + // Always use semicolons at end of statement + semi: [2, 'always'], + // Prefer single quotes quotes: [ 2, - "double", + 'single', { avoidEscape: true, }, ], - - "use-isnan": "error", - - "@typescript-eslint/adjacent-overload-signatures": "error", - - "@typescript-eslint/array-type": [ - "error", + // isNaN(i) Number.isNaN(i) instead of i === NaN + 'use-isnan': 'error', + // Use MIT file header + 'pluginHeader/header': [ + 2, + 'block', + [{ pattern: 'MIT License|DO NOT EDIT MANUALLY!' }], + ], + 'no-restricted-imports': [ + 'error', { - default: "array-simple", + paths: [ + { + name: 'vscode-jsonrpc', + importNames: ['CancellationToken'], + message: + 'Import "CancellationToken" via "Cancellation.CancellationToken" from "langium", or directly from "./utils/cancellation.ts" within Langium.', + }, + { + name: 'vscode-jsonrpc/', + importNames: ['CancellationToken'], + message: + 'Import "CancellationToken" via "Cancellation.CancellationToken" from "langium", or directly from "./utils/cancellation.ts" within Langium.', + }, + ], + patterns: [ + { + group: ['vscode-jsonrpc'], + importNamePattern: '^(?!CancellationToken)', + message: + 'Don\'t import types or symbols from "vscode-jsonrpc" (package index), as that brings a large overhead in bundle size. Import from "vscode-jsonrpc/lib/common/...js" and add a // eslint-disable..., if really necessary.', + }, + ], }, ], + // use @typescript-eslint/no-unused-vars instead + 'no-unused-vars': 'off', + // Disallow unnecessary escape characters + 'no-useless-escape': 'off', - "@typescript-eslint/no-empty-object-type": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-misused-new": "error", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/parameter-properties": "error", - - "@typescript-eslint/no-unused-vars": [ - "error", + // List of [@typescript-eslint rules](https://typescript-eslint.io/rules/) + // Require that function overload signatures be consecutive + '@typescript-eslint/adjacent-overload-signatures': 'error', + // Require consistently using either T[] or Array for arrays + '@typescript-eslint/array-type': [ + 'error', + { + default: 'array-simple', + }, + ], + // Allow accidentally using the 'empty object' type. Note, this is different from Langium's settings. + '@typescript-eslint/no-empty-object-type': 'off', + // Disallow explicit type declarations for variables or parameters initialized to a number, string, or boolean + '@typescript-eslint/no-inferrable-types': 'off', + // Disallow using the unsafe built-in Function type + '@typescript-eslint/no-unsafe-function-type': 'error', + // Disallow using confusing built-in primitive class wrappers + '@typescript-eslint/no-wrapper-object-types': 'error', + // Disallow the `any` type + '@typescript-eslint/no-explicit-any': 'error', + // Enforce valid definition of `new` and `constructor` + '@typescript-eslint/no-misused-new': 'error', + // Disallow TypeScript namespaces + '@typescript-eslint/no-namespace': 'off', + // Disallow non-null assertions using the ! postfix operator + '@typescript-eslint/no-non-null-assertion': 'off', + // Require or disallow parameter properties in class constructors + '@typescript-eslint/parameter-properties': 'off', + // Disallow unused variables + '@typescript-eslint/no-unused-vars': [ + 'error', { - argsIgnorePattern: "^_", + caughtErrorsIgnorePattern: '^_', + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', }, ], + // isallow require statements except in import statements + '@typescript-eslint/no-var-requires': 'error', + // Enforce the use of `for-of` loop over the standard `for` loop where possible + '@typescript-eslint/prefer-for-of': 'error', + // Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules + '@typescript-eslint/prefer-namespace-keyword': 'error', + // Disallow certain triple slash directives in favor of ES6-style import declarations + '@typescript-eslint/triple-slash-reference': 'error', + // Disallow conditionals where the type is always truthy or always falsy + '@typescript-eslint/no-unnecessary-condition': 'off', + // Disallow unused expressions + '@typescript-eslint/no-unused-expressions': 'off', + // Enforce consistent usage of type imports + '@typescript-eslint/consistent-type-imports': 'error', - "@typescript-eslint/no-var-requires": "error", - "@typescript-eslint/prefer-for-of": "error", - "@typescript-eslint/prefer-namespace-keyword": "error", - "@typescript-eslint/triple-slash-reference": "error", + // List of [@stylistic rules](https://eslint.style/rules) + // Enforce consistent indentation + '@stylistic/indent': 'error', + // Require consistent spacing around type annotations + '@stylistic/type-annotation-spacing': 'error', }, }, ]); diff --git a/examples/lox/src/cli/cli-util.ts b/examples/lox/src/cli/cli-util.ts index b0c5ccf3..af12988f 100644 --- a/examples/lox/src/cli/cli-util.ts +++ b/examples/lox/src/cli/cli-util.ts @@ -4,12 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { AstNode, LangiumCoreServices, LangiumDocument } from "langium"; -import chalk from "chalk"; -import * as path from "node:path"; -import * as fs from "node:fs"; -import { URI } from "langium"; -import { LangiumServices } from "langium/lsp"; +import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium'; +import chalk from 'chalk'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import { URI } from 'langium'; +import type { LangiumServices } from 'langium/lsp'; export async function extractDocument( fileName: string, @@ -42,7 +42,7 @@ export async function extractDocument( (e) => e.severity === 1, ); if (validationErrors.length > 0) { - console.error(chalk.red("There are validation errors:")); + console.error(chalk.red('There are validation errors:')); for (const validationError of validationErrors) { console.error( chalk.red( @@ -74,10 +74,10 @@ export function extractDestinationAndName( ): FilePathData { filePath = path .basename(filePath, path.extname(filePath)) - .replace(/[.-]/g, ""); + .replace(/[.-]/g, ''); return { destination: - destination ?? path.join(path.dirname(filePath), "generated"), + destination ?? path.join(path.dirname(filePath), 'generated'), name: path.basename(filePath), }; } diff --git a/examples/lox/src/cli/main.ts b/examples/lox/src/cli/main.ts index 07a34fe7..e6f0544a 100644 --- a/examples/lox/src/cli/main.ts +++ b/examples/lox/src/cli/main.ts @@ -4,18 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Command } from "commander"; -import { LoxLanguageMetaData } from "../language/generated/module.js"; -import { extractDestinationAndName } from "./cli-util.js"; -import * as url from "node:url"; -import * as fsp from "node:fs/promises"; -import * as path from "node:path"; -import * as fs from "node:fs"; +import { Command } from 'commander'; +import { LoxLanguageMetaData } from '../language/generated/module.js'; +import { extractDestinationAndName } from './cli-util.js'; +import * as url from 'node:url'; +import * as fsp from 'node:fs/promises'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); -const packagePath = path.resolve(__dirname, "..", "..", "package.json"); -const packageContent = await fsp.readFile(packagePath, "utf-8"); +const packagePath = path.resolve(__dirname, '..', '..', 'package.json'); +const packageContent = await fsp.readFile(packagePath, 'utf-8'); export const generateAction = async ( fileName: string, @@ -34,23 +34,23 @@ export type GenerateOptions = { destination?: string; }; -export default function (): void { +export default function(): void { const program = new Command(); program.version(JSON.parse(packageContent).version); - const fileExtensions = LoxLanguageMetaData.fileExtensions.join(", "); + const fileExtensions = LoxLanguageMetaData.fileExtensions.join(', '); program - .command("generate") + .command('generate') .argument( - "", + '', `source file (possible file extensions: ${fileExtensions})`, ) .option( - "-d, --destination ", - "destination directory of generating", + '-d, --destination ', + 'destination directory of generating', ) - .description("generates from the source file") + .description('generates from the source file') .action(generateAction); program.parse(process.argv); diff --git a/examples/lox/src/extension/main.ts b/examples/lox/src/extension/main.ts index af6fb1f1..eb8d5db9 100644 --- a/examples/lox/src/extension/main.ts +++ b/examples/lox/src/extension/main.ts @@ -7,10 +7,10 @@ import type { LanguageClientOptions, ServerOptions, -} from "vscode-languageclient/node.js"; -import * as vscode from "vscode"; -import * as path from "node:path"; -import { LanguageClient, TransportKind } from "vscode-languageclient/node.js"; +} from 'vscode-languageclient/node.js'; +import * as vscode from 'vscode'; +import * as path from 'node:path'; +import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js'; let client: LanguageClient; @@ -29,15 +29,15 @@ export function deactivate(): Thenable | undefined { function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { const serverModule = context.asAbsolutePath( - path.join("out", "language", "main.cjs"), + path.join('out', 'language', 'main.cjs'), ); // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached. const debugOptions = { execArgv: [ - "--nolazy", - `--inspect${process.env.DEBUG_BREAK ? "-brk" : ""}=${process.env.DEBUG_SOCKET || "6009"}`, + '--nolazy', + `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`, ], }; @@ -53,12 +53,12 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { }; const fileSystemWatcher = - vscode.workspace.createFileSystemWatcher("**/*.lox"); + vscode.workspace.createFileSystemWatcher('**/*.lox'); context.subscriptions.push(fileSystemWatcher); // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: "file", language: "lox" }], + documentSelector: [{ scheme: 'file', language: 'lox' }], synchronize: { // Notify the server about file changes to files contained in the workspace fileEvents: fileSystemWatcher, @@ -67,8 +67,8 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { // Create the language client and start the client. const client = new LanguageClient( - "lox", - "Lox", + 'lox', + 'Lox', serverOptions, clientOptions, ); diff --git a/examples/lox/src/language/lox-linker.ts b/examples/lox/src/language/lox-linker.ts index b9180136..43a6c05b 100644 --- a/examples/lox/src/language/lox-linker.ts +++ b/examples/lox/src/language/lox-linker.ts @@ -4,22 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { - AstNodeDescription, - DefaultLinker, - LinkingError, - ReferenceInfo, -} from "langium"; -import { isType } from "typir"; -import { TypirLangiumServices } from "typir-langium"; +import type { AstNodeDescription, LinkingError, ReferenceInfo } from 'langium'; +import { DefaultLinker } from 'langium'; +import { isType } from 'typir'; +import type { TypirLangiumServices } from 'typir-langium'; +import type { LoxAstType } from './generated/ast.js'; import { isClass, isFunctionDeclaration, isMemberCall, isMethodMember, - LoxAstType, -} from "./generated/ast.js"; -import { LoxServices } from "./lox-module.js"; +} from './generated/ast.js'; +import type { LoxServices } from './lox-module.js'; export class LoxLinker extends DefaultLinker { protected readonly typir: TypirLangiumServices; diff --git a/examples/lox/src/language/lox-module.ts b/examples/lox/src/language/lox-module.ts index 4a8d13a9..e9e900bc 100644 --- a/examples/lox/src/language/lox-module.ts +++ b/examples/lox/src/language/lox-module.ts @@ -4,33 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { LangiumSharedCoreServices, Module, PartialLangiumCoreServices, - createDefaultCoreModule, - inject, -} from "langium"; -import { +} from 'langium'; +import { createDefaultCoreModule, inject } from 'langium'; +import type { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, - createDefaultSharedModule, -} from "langium/lsp"; +} from 'langium/lsp'; +import { createDefaultSharedModule } from 'langium/lsp'; +import type { TypirLangiumServices } from 'typir-langium'; import { - TypirLangiumServices, createTypirLangiumServices, initializeLangiumTypirServices, -} from "typir-langium"; -import { LoxAstType, reflection } from "./generated/ast.js"; +} from 'typir-langium'; +import type { LoxAstType } from './generated/ast.js'; +import { reflection } from './generated/ast.js'; import { LoxGeneratedModule, LoxGeneratedSharedModule, -} from "./generated/module.js"; -import { LoxLinker } from "./lox-linker.js"; -import { LoxScopeProvider } from "./lox-scope.js"; -import { LoxTypeSystem } from "./lox-type-checking.js"; -import { LoxValidationRegistry, LoxValidator } from "./lox-validator.js"; +} from './generated/module.js'; +import { LoxLinker } from './lox-linker.js'; +import { LoxScopeProvider } from './lox-scope.js'; +import { LoxTypeSystem } from './lox-type-checking.js'; +import { LoxValidationRegistry, LoxValidator } from './lox-validator.js'; /** * Declaration of custom services - add your own service classes here. diff --git a/examples/lox/src/language/lox-scope.ts b/examples/lox/src/language/lox-scope.ts index ede00d76..0ed8cb5f 100644 --- a/examples/lox/src/language/lox-scope.ts +++ b/examples/lox/src/language/lox-scope.ts @@ -4,24 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { - AstUtils, - DefaultScopeProvider, - EMPTY_SCOPE, - ReferenceInfo, - Scope, -} from "langium"; -import { isClassType } from "typir"; -import { TypirLangiumServices } from "typir-langium"; -import { - Class, - isClass, - isMemberCall, - LoxAstType, - MemberCall, -} from "./generated/ast.js"; -import { LoxServices } from "./lox-module.js"; -import { getClassChain } from "./lox-utils.js"; +import type { ReferenceInfo, Scope } from 'langium'; +import { AstUtils, DefaultScopeProvider, EMPTY_SCOPE } from 'langium'; +import { isClassType } from 'typir'; +import type { TypirLangiumServices } from 'typir-langium'; +import type { Class, LoxAstType, MemberCall } from './generated/ast.js'; +import { isClass, isMemberCall } from './generated/ast.js'; +import type { LoxServices } from './lox-module.js'; +import { getClassChain } from './lox-utils.js'; // import { isClassType } from './type-system/descriptions.js'; // import { getClassChain, inferType } from './type-system/infer.js'; @@ -35,11 +25,11 @@ export class LoxScopeProvider extends DefaultScopeProvider { override getScope(context: ReferenceInfo): Scope { // target element of member calls - if (context.property === "element" && isMemberCall(context.container)) { + if (context.property === 'element' && isMemberCall(context.container)) { // for now, `this` and `super` simply target the container class type if ( - context.reference.$refText === "this" || - context.reference.$refText === "super" + context.reference.$refText === 'this' || + context.reference.$refText === 'super' ) { const classItem = AstUtils.getContainerOfType( context.container, diff --git a/examples/lox/src/language/lox-type-checking.ts b/examples/lox/src/language/lox-type-checking.ts index f992e092..a0cbcbb3 100644 --- a/examples/lox/src/language/lox-type-checking.ts +++ b/examples/lox/src/language/lox-type-checking.ts @@ -4,32 +4,36 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, assertUnreachable } from "langium"; -import { +import type { AstNode } from 'langium'; +import { AstUtils, assertUnreachable } from 'langium'; +import type { CreateFieldDetails, CreateMethodDetails, CreateParameterDetails, FunctionType, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand, - InferenceRuleNotApplicable, - NO_PARAMETER_NAME, TypeInitializer, TypirServices, ValidationProblemAcceptor, -} from "typir"; -import { +} from 'typir'; +import { InferenceRuleNotApplicable, NO_PARAMETER_NAME } from 'typir'; +import type { TypirLangiumServices, LangiumTypeSystemDefinition, -} from "typir-langium"; +} from 'typir-langium'; +import type { + ForStatement, + IfStatement, + LoxAstType, + VariableDeclaration, + WhileStatement, +} from './generated/ast.js'; import { BinaryExpression, BooleanLiteral, Class, - ForStatement, FunctionDeclaration, - IfStatement, - LoxAstType, MemberCall, MethodMember, NilLiteral, @@ -39,15 +43,13 @@ import { StringLiteral, TypeReference, UnaryExpression, - VariableDeclaration, - WhileStatement, isClass, isFieldMember, isFunctionDeclaration, isMethodMember, isParameter, isVariableDeclaration, -} from "./generated/ast.js"; +} from './generated/ast.js'; /* eslint-disable @typescript-eslint/no-unused-vars */ export class LoxTypeSystem implements LangiumTypeSystemDefinition { @@ -55,41 +57,41 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { // primitive types // typeBool, typeNumber and typeVoid are specific types for OX, ... const typeBool = typir.factory.Primitives.create({ - primitiveName: "boolean", + primitiveName: 'boolean', }) .inferenceRule({ languageKey: BooleanLiteral }) // this is the more performant notation compared to ... // .inferenceRule({ filter: isBooleanLiteral }) // ... this alternative solution, but they provide the same functionality .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "boolean", + matching: (node: TypeReference) => node.primitive === 'boolean', }) // this is the more performant notation compared to ... // .inferenceRule({ filter: isTypeReference, matching: node => node.primitive === 'boolean' }) // ... this "easier" notation, but they provide the same functionality .finish(); // ... but their primitive kind is provided/preset by Typir const typeNumber = typir.factory.Primitives.create({ - primitiveName: "number", + primitiveName: 'number', }) .inferenceRule({ languageKey: NumberLiteral }) .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "number", + matching: (node: TypeReference) => node.primitive === 'number', }) .finish(); const typeString = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ languageKey: StringLiteral }) .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "string", + matching: (node: TypeReference) => node.primitive === 'string', }) .finish(); const typeVoid = typir.factory.Primitives.create({ - primitiveName: "void", + primitiveName: 'void', }) .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "void", + matching: (node: TypeReference) => node.primitive === 'void', }) .inferenceRule({ languageKey: PrintStatement }) .inferenceRule({ @@ -98,7 +100,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { }) .finish(); const typeNil = typir.factory.Primitives.create({ - primitiveName: "nil", + primitiveName: 'nil', }) .inferenceRule({ languageKey: NilLiteral }) .finish(); // 'nil' is only assignable to variables with a class as type in the LOX implementation here @@ -130,7 +132,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { }; // binary operators: numbers => number - for (const operator of ["-", "*", "/"]) { + for (const operator of ['-', '*', '/']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -143,7 +145,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { .finish(); } typir.factory.Operators.createBinary({ - name: "+", + name: '+', signatures: [ { left: typeNumber, right: typeNumber, return: typeNumber }, { left: typeString, right: typeString, return: typeString }, @@ -155,7 +157,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { .finish(); // binary operators: numbers => boolean - for (const operator of ["<", "<=", ">", ">="]) { + for (const operator of ['<', '<=', '>', '>=']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -169,7 +171,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { } // binary operators: booleans => boolean - for (const operator of ["and", "or"]) { + for (const operator of ['and', 'or']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -183,7 +185,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { } // ==, != for all data types (the warning for different types is realized below) - for (const operator of ["==", "!="]) { + for (const operator of ['==', '!=']) { typir.factory.Operators.createBinary({ name: operator, signature: { left: typeAny, right: typeAny, return: typeBool }, @@ -203,10 +205,10 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { node.right, accept, (actual, expected) => ({ - message: `This comparison will always return '${node.operator === "==" ? "false" : "true"}' as '${node.left.$cstNode?.text}' and '${node.right.$cstNode?.text}' have the different types '${actual.name}' and '${expected.name}'.`, + message: `This comparison will always return '${node.operator === '==' ? 'false' : 'true'}' as '${node.left.$cstNode?.text}' and '${node.right.$cstNode?.text}' have the different types '${actual.name}' and '${expected.name}'.`, languageNode: node, // inside the BinaryExpression ... - languageProperty: "operator", // ... mark the '==' or '!=' token, i.e. the 'operator' property - severity: "warning", + languageProperty: 'operator', // ... mark the '==' or '!=' token, i.e. the 'operator' property + severity: 'warning', // (The use of "node.right" and "node.left" without casting is possible, since the type checks of the given properties for the actual inference rule are reused for the validation.) }), ), @@ -215,7 +217,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { } // = for SuperType = SubType (Note that this implementation of LOX realized assignments as operators!) typir.factory.Operators.createBinary({ - name: "=", + name: '=', signature: { left: typeAny, right: typeAny, return: typeAny }, }) .inferenceRule({ @@ -228,7 +230,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { accept, (actual, expected) => ({ message: `The expression '${node.right.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.left.$cstNode?.text}' with type '${expected.name}'`, - languageProperty: "value", + languageProperty: 'value', }), ), }) @@ -236,13 +238,13 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { // unary operators typir.factory.Operators.createUnary({ - name: "!", + name: '!', signature: { operand: typeBool, return: typeBool }, }) .inferenceRule(unaryInferenceRule) .finish(); typir.factory.Operators.createUnary({ - name: "-", + name: '-', signature: { operand: typeNumber, return: typeNumber }, }) .inferenceRule(unaryInferenceRule) @@ -302,7 +304,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { // check for unique class declarations const uniqueClassValidator = typir.factory.Classes.createUniqueClassValidation({ - registration: "MYSELF", + registration: 'MYSELF', }); // check for unique method declarations typir.factory.Classes.createUniqueMethodValidation({ @@ -395,9 +397,9 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { // explicitly declare, that 'nil' can be assigned to any Class variable classType.addListener((type) => { typir.Conversion.markAsConvertible( - typir.factory.Primitives.get({ primitiveName: "nil" })!, + typir.factory.Primitives.get({ primitiveName: 'nil' })!, type, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); }); // The following idea does not work, since variables in LOX have a concrete class type and not an "any class" type: @@ -470,13 +472,13 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { | FunctionDeclaration | MethodMember | undefined = AstUtils.getContainerOfType( - node, - (node) => isFunctionDeclaration(node) || isMethodMember(node), - ); + node, + (node) => isFunctionDeclaration(node) || isMethodMember(node), + ); if ( callableDeclaration && callableDeclaration.returnType.primitive && - callableDeclaration.returnType.primitive !== "void" && + callableDeclaration.returnType.primitive !== 'void' && node.value ) { // the return value must fit to the return type of the function / method @@ -486,7 +488,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { accept, (actual, expected) => ({ message: `The expression '${node.value!.$cstNode?.text}' of type '${actual.name}' is not usable as return value for the function '${callableDeclaration.name}' with return type '${expected.name}'.`, - languageProperty: "value", + languageProperty: 'value', }), ); } @@ -498,7 +500,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { typir: TypirServices, ): void { const typeVoid = typir.factory.Primitives.get({ - primitiveName: "void", + primitiveName: 'void', })!; typir.validation.Constraints.ensureNodeHasNotType( node, @@ -506,7 +508,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { accept, () => ({ message: "Variable can't be declared with a type 'void'.", - languageProperty: "type", + languageProperty: 'type', }), ); typir.validation.Constraints.ensureNodeIsAssignable( @@ -515,7 +517,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { accept, (actual, expected) => ({ message: `The expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.name}' with type '${expected.name}'`, - languageProperty: "value", + languageProperty: 'value', }), ); } @@ -526,7 +528,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { typir: TypirServices, ): void { const typeBool = typir.factory.Primitives.get({ - primitiveName: "boolean", + primitiveName: 'boolean', })!; typir.validation.Constraints.ensureNodeIsAssignable( node.condition, @@ -534,7 +536,7 @@ export class LoxTypeSystem implements LangiumTypeSystemDefinition { accept, () => ({ message: "Conditions need to be evaluated to 'boolean'.", - languageProperty: "condition", + languageProperty: 'condition', }), ); } diff --git a/examples/lox/src/language/lox-utils.ts b/examples/lox/src/language/lox-utils.ts index 6021d8d0..099db39f 100644 --- a/examples/lox/src/language/lox-utils.ts +++ b/examples/lox/src/language/lox-utils.ts @@ -4,7 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Class } from "./generated/ast.js"; +import type { Class } from './generated/ast.js'; export function getClassChain(classItem: Class): Class[] { const set = new Set(); diff --git a/examples/lox/src/language/lox-validator.ts b/examples/lox/src/language/lox-validator.ts index 1b14be51..ade33b09 100644 --- a/examples/lox/src/language/lox-validator.ts +++ b/examples/lox/src/language/lox-validator.ts @@ -4,13 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { - ValidationAcceptor, - ValidationChecks, - ValidationRegistry, -} from "langium"; -import { LoxAstType, VariableDeclaration } from "./generated/ast.js"; -import type { LoxServices } from "./lox-module.js"; +import type { ValidationAcceptor, ValidationChecks } from 'langium'; +import { ValidationRegistry } from 'langium'; +import type { LoxAstType, VariableDeclaration } from './generated/ast.js'; +import type { LoxServices } from './lox-module.js'; /** * Registry for validation checks. @@ -37,11 +34,11 @@ export class LoxValidator { ): void { if (!decl.type && !decl.value) { accept( - "error", - "Variables require a type hint or an assignment at creation", + 'error', + 'Variables require a type hint or an assignment at creation', { node: decl, - property: "name", + property: 'name', }, ); } diff --git a/examples/lox/src/language/main.ts b/examples/lox/src/language/main.ts index 24b1283f..27260a13 100644 --- a/examples/lox/src/language/main.ts +++ b/examples/lox/src/language/main.ts @@ -4,13 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { startLanguageServer } from "langium/lsp"; -import { NodeFileSystem } from "langium/node"; +import { startLanguageServer } from 'langium/lsp'; +import { NodeFileSystem } from 'langium/node'; import { createConnection, ProposedFeatures, -} from "vscode-languageserver/node.js"; -import { createLoxServices } from "./lox-module.js"; +} from 'vscode-languageserver/node.js'; +import { createLoxServices } from './lox-module.js'; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); diff --git a/examples/lox/syntaxes/lox.monarch.ts b/examples/lox/syntaxes/lox.monarch.ts index 134d06de..b241533b 100644 --- a/examples/lox/syntaxes/lox.monarch.ts +++ b/examples/lox/syntaxes/lox.monarch.ts @@ -1,45 +1,51 @@ +/****************************************************************************** + * Copyright 2025 TypeFox GmbH + * This program and the accompanying materials are made available under the + * terms of the MIT License, which is available in the project root. + ******************************************************************************/ + // Monarch syntax highlighting for the lox language. export default { keywords: [ - "and", - "boolean", - "class", - "else", - "false", - "for", - "fun", - "if", - "nil", - "number", - "or", - "print", - "return", - "string", - "super", - "this", - "true", - "var", - "void", - "while", + 'and', + 'boolean', + 'class', + 'else', + 'false', + 'for', + 'fun', + 'if', + 'nil', + 'number', + 'or', + 'print', + 'return', + 'string', + 'super', + 'this', + 'true', + 'var', + 'void', + 'while', ], operators: [ - "!", - "!=", - "*", - "+", - ",", - "-", - ".", - "/", - ":", - ";", - "<", - "<=", - "=", - "==", - "=>", - ">", - ">=", + '!', + '!=', + '*', + '+', + ',', + '-', + '.', + '/', + ':', + ';', + '<', + '<=', + '=', + '==', + '=>', + '>', + '>=', ], symbols: /!|!=|\(|\)|\*|\+|,|-|\.|\/|:|;|<|<=|=|==|=>|>|>=|\{|\}/, @@ -49,33 +55,33 @@ export default { regex: /[_a-zA-Z][\w_]*/, action: { cases: { - "@keywords": { token: "keyword" }, - "@default": { token: "ID" }, + '@keywords': { token: 'keyword' }, + '@default': { token: 'ID' }, }, }, }, - { regex: /[0-9]+(\.[0-9]+)?/, action: { token: "number" } }, - { regex: /"[^"]*"/, action: { token: "string" } }, - { include: "@whitespace" }, + { regex: /[0-9]+(\.[0-9]+)?/, action: { token: 'number' } }, + { regex: /"[^"]*"/, action: { token: 'string' } }, + { include: '@whitespace' }, { regex: /@symbols/, action: { cases: { - "@operators": { token: "operator" }, - "@default": { token: "" }, + '@operators': { token: 'operator' }, + '@default': { token: '' }, }, }, }, ], whitespace: [ - { regex: /\s+/, action: { token: "white" } }, - { regex: /\/\*/, action: { token: "comment", next: "@comment" } }, - { regex: /\/\/[^\n\r]*/, action: { token: "comment" } }, + { regex: /\s+/, action: { token: 'white' } }, + { regex: /\/\*/, action: { token: 'comment', next: '@comment' } }, + { regex: /\/\/[^\n\r]*/, action: { token: 'comment' } }, ], comment: [ - { regex: /[^*]+/, action: { token: "comment" } }, - { regex: /\*\//, action: { token: "comment", next: "@pop" } }, - { regex: /[*]/, action: { token: "comment" } }, + { regex: /[^*]+/, action: { token: 'comment' } }, + { regex: /\*\//, action: { token: 'comment', next: '@pop' } }, + { regex: /[*]/, action: { token: 'comment' } }, ], }, }; diff --git a/examples/lox/test/lox-type-checking-classes.test.ts b/examples/lox/test/lox-type-checking-classes.test.ts index a5d79897..7f82804f 100644 --- a/examples/lox/test/lox-type-checking-classes.test.ts +++ b/examples/lox/test/lox-type-checking-classes.test.ts @@ -4,18 +4,16 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstUtils } from "langium"; -import { isClassType, isPrimitiveType } from "typir"; -import { expectToBeType, expectTypirTypes } from "typir/test"; -import { describe, expect, test } from "vitest"; -import { - isVariableDeclaration, - LoxProgram, -} from "../src/language/generated/ast.js"; -import { loxServices, validateLox } from "./lox-type-checking-utils.js"; +import { AstUtils } from 'langium'; +import { isClassType, isPrimitiveType } from 'typir'; +import { expectToBeType, expectTypirTypes } from 'typir/test'; +import { describe, expect, test } from 'vitest'; +import type { LoxProgram } from '../src/language/generated/ast.js'; +import { isVariableDeclaration } from '../src/language/generated/ast.js'; +import { loxServices, validateLox } from './lox-type-checking-utils.js'; -describe("Test type checking for classes", () => { - test("Class inheritance for assignments: correct", async () => { +describe('Test type checking for classes', () => { + test('Class inheritance for assignments: correct', async () => { await validateLox( ` class MyClass1 { name: string age: number } @@ -27,12 +25,12 @@ describe("Test type checking for classes", () => { expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); - test("Class inheritance for assignments: wrong", async () => { + test('Class inheritance for assignments: wrong', async () => { await validateLox( ` class MyClass1 { name: string age: number } @@ -44,12 +42,12 @@ describe("Test type checking for classes", () => { expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); - test("Class fields: correct values", async () => { + test('Class fields: correct values', async () => { await validateLox( ` class MyClass1 { name: string age: number } @@ -59,10 +57,10 @@ describe("Test type checking for classes", () => { `, 0, ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Class fields: wrong values", async () => { + test('Class fields: wrong values', async () => { await validateLox( ` class MyClass1 { name: string age: number } @@ -72,24 +70,24 @@ describe("Test type checking for classes", () => { `, 2, ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Classes must be unique by name 2", async () => { + test('Classes must be unique by name 2', async () => { await validateLox( ` class MyClass1 { } class MyClass1 { } `, [ - "Declared classes need to be unique (MyClass1).", - "Declared classes need to be unique (MyClass1).", + 'Declared classes need to be unique (MyClass1).', + 'Declared classes need to be unique (MyClass1).', ], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Classes must be unique by name 3", async () => { + test('Classes must be unique by name 3', async () => { await validateLox( ` class MyClass2 { } @@ -97,17 +95,17 @@ describe("Test type checking for classes", () => { class MyClass2 { } `, [ - "Declared classes need to be unique (MyClass2).", - "Declared classes need to be unique (MyClass2).", - "Declared classes need to be unique (MyClass2).", + 'Declared classes need to be unique (MyClass2).', + 'Declared classes need to be unique (MyClass2).', + 'Declared classes need to be unique (MyClass2).', ], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass2"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass2'); }); }); -describe("Class literals", () => { - test("Class literals 1", async () => { +describe('Class literals', () => { + test('Class literals 1', async () => { await validateLox( ` class MyClass { name: string age: number } @@ -115,10 +113,10 @@ describe("Class literals", () => { `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); - test("Class literals 2", async () => { + test('Class literals 2', async () => { await validateLox( ` class MyClass { name: string age: number } @@ -126,10 +124,10 @@ describe("Class literals", () => { `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); - test("Class literals 3", async () => { + test('Class literals 3', async () => { await validateLox( ` class MyClass1 {} @@ -142,12 +140,12 @@ describe("Class literals", () => { expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); - test("nil is assignable to any Class", async () => { + test('nil is assignable to any Class', async () => { await validateLox( ` class MyClass1 {} @@ -162,14 +160,14 @@ describe("Class literals", () => { expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); }); -describe("Class field access", () => { - test("simple class", async () => { +describe('Class field access', () => { + test('simple class', async () => { const program = ( await validateLox( ` @@ -181,11 +179,11 @@ describe("Class field access", () => { [], ) ).parseResult.value as LoxProgram; - checkVariableDeclaration(program, "v2", "string"); - checkVariableDeclaration(program, "v3", "number"); + checkVariableDeclaration(program, 'v2', 'string'); + checkVariableDeclaration(program, 'v3', 'number'); }); - test("different classes with switched properties", async () => { + test('different classes with switched properties', async () => { const program = ( await validateLox( ` @@ -201,16 +199,16 @@ describe("Class field access", () => { [], ) ).parseResult.value as LoxProgram; - checkVariableDeclaration(program, "v1name", "string"); - checkVariableDeclaration(program, "v1age", "number"); - checkVariableDeclaration(program, "v2name", "number"); - checkVariableDeclaration(program, "v2age", "string"); + checkVariableDeclaration(program, 'v1name', 'string'); + checkVariableDeclaration(program, 'v1age', 'number'); + checkVariableDeclaration(program, 'v2name', 'number'); + checkVariableDeclaration(program, 'v2age', 'string'); }); function checkVariableDeclaration( program: LoxProgram, name: string, - expectedType: "string" | "number", + expectedType: 'string' | 'number', ): void { const variables = AstUtils.streamAllContents(program) .filter(isVariableDeclaration) diff --git a/examples/lox/test/lox-type-checking-cycles.test.ts b/examples/lox/test/lox-type-checking-cycles.test.ts index 492bca74..0c376fba 100644 --- a/examples/lox/test/lox-type-checking-cycles.test.ts +++ b/examples/lox/test/lox-type-checking-cycles.test.ts @@ -4,17 +4,17 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isClassType, isFunctionType } from "typir"; -import { expectTypirTypes } from "typir/test"; -import { describe, test } from "vitest"; +import { isClassType, isFunctionType } from 'typir'; +import { expectTypirTypes } from 'typir/test'; +import { describe, test } from 'vitest'; import { loxServices, operatorNames, validateLox, -} from "./lox-type-checking-utils.js"; +} from './lox-type-checking-utils.js'; -describe("Cyclic type definitions where a Class is declared and already used", () => { - test("Class with field of its own type", async () => { +describe('Cyclic type definitions where a Class is declared and already used', () => { + test('Class with field of its own type', async () => { await validateLox( ` class Node { @@ -23,10 +23,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "Node"); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); }); - test("Two Classes with fields with the other Class as type", async () => { + test('Two Classes with fields with the other Class as type', async () => { await validateLox( ` class A { @@ -38,10 +38,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); - test("Three Classes with fields with one of the other Classes as type", async () => { + test('Three Classes with fields with one of the other Classes as type', async () => { await validateLox( ` class A { @@ -56,10 +56,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B", "C"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); }); - test("Three Classes with fields with two of the other Classes as type", async () => { + test('Three Classes with fields with two of the other Classes as type', async () => { await validateLox( ` class A { @@ -77,10 +77,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B", "C"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); }); - test("Class with field of its own type and another dependency", async () => { + test('Class with field of its own type and another dependency', async () => { await validateLox( ` class Node { @@ -93,10 +93,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "Node", "Another"); + expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); }); - test("Two Classes with a field of its own type and cyclic dependencies to each other", async () => { + test('Two Classes with a field of its own type and cyclic dependencies to each other', async () => { await validateLox( ` class Node { @@ -110,10 +110,10 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "Node", "Another"); + expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); }); - test("Having two declarations for the delayed class A, but only one type A in the type system", async () => { + test('Having two declarations for the delayed class A, but only one type A in the type system', async () => { await validateLox( ` class A { @@ -126,15 +126,15 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [ // Typir works with this, but for LOX these validation errors are produced: - "Declared classes need to be unique (A).", - "Declared classes need to be unique (A).", + 'Declared classes need to be unique (A).', + 'Declared classes need to be unique (A).', ], ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); - test("Having three declarations for the delayed class A, but only one type A in the type system", async () => { + test('Having three declarations for the delayed class A, but only one type A in the type system', async () => { await validateLox( ` class A { @@ -150,16 +150,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [ // Typir works with this, but for LOX these validation errors are produced: - "Declared classes need to be unique (A).", - "Declared classes need to be unique (A).", - "Declared classes need to be unique (A).", + 'Declared classes need to be unique (A).', + 'Declared classes need to be unique (A).', + 'Declared classes need to be unique (A).', ], ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); - test("Having two declarations for class A waiting for B, while B itself depends on A", async () => { + test('Having two declarations for class A waiting for B, while B itself depends on A', async () => { await validateLox( ` class A { @@ -174,15 +174,15 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [ // Typir works with this, but for LOX these validation errors are produced: - "Declared classes need to be unique (A).", - "Declared classes need to be unique (A).", + 'Declared classes need to be unique (A).', + 'Declared classes need to be unique (A).', ], ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); - test("Class with method: cycle with return type", async () => { + test('Class with method: cycle with return type', async () => { await validateLox( ` class Node { @@ -191,16 +191,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "Node"); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Class with method: cycle with input parameter type", async () => { + test('Class with method: cycle with input parameter type', async () => { await validateLox( ` class Node { @@ -209,16 +209,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "Node"); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Two different Classes with the same method (type) should result in only one method type", async () => { + test('Two different Classes with the same method (type) should result in only one method type', async () => { await validateLox( ` class A { @@ -232,16 +232,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Two different Classes depend on each other regarding their methods return type", async () => { + test('Two different Classes depend on each other regarding their methods return type', async () => { await validateLox( ` class A { @@ -255,17 +255,17 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", - "myMethod", + 'myMethod', + 'myMethod', ...operatorNames, ); }); - test("Two different Classes with the same method which has one of these classes as return type", async () => { + test('Two different Classes with the same method which has one of these classes as return type', async () => { await validateLox( ` class A { @@ -279,16 +279,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Same delayed function type is used by a function declaration and a method declaration", async () => { + test('Same delayed function type is used by a function declaration and a method declaration', async () => { await validateLox( ` class A { @@ -299,16 +299,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Two class declarations A with the same delayed method which depends on the class B", async () => { + test('Two class declarations A with the same delayed method which depends on the class B', async () => { await validateLox( ` class A { @@ -321,21 +321,21 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [ // Typir works with this, but for LOX these validation errors are produced: - "Declared classes need to be unique (A).", - "Declared classes need to be unique (A).", + 'Declared classes need to be unique (A).', + 'Declared classes need to be unique (A).', ], ); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices.typir, isClassType, "A", "B"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Mix of dependencies in classes: 1 method and 1 field", async () => { + test('Mix of dependencies in classes: 1 method and 1 field', async () => { await validateLox( ` class A { @@ -347,16 +347,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B1"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Mix of dependencies in classes: 1 method and 2 fields (order 1)", async () => { + test('Mix of dependencies in classes: 1 method and 2 fields (order 1)', async () => { await validateLox( ` class B1 { @@ -371,16 +371,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B1", "B2"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("Mix of dependencies in classes: 1 method and 2 fields (order 2)", async () => { + test('Mix of dependencies in classes: 1 method and 2 fields (order 2)', async () => { await validateLox( ` class A { @@ -395,16 +395,16 @@ describe("Cyclic type definitions where a Class is declared and already used", ( `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "A", "B1", "B2"); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", + 'myMethod', ...operatorNames, ); }); - test("The same class is involved into two dependency cycles", async () => { + test('The same class is involved into two dependency cycles', async () => { await validateLox( ` class A { @@ -429,23 +429,23 @@ describe("Cyclic type definitions where a Class is declared and already used", ( expectTypirTypes( loxServices.typir, isClassType, - "A", - "B1", - "B2", - "C1", - "C2", + 'A', + 'B1', + 'B2', + 'C1', + 'C2', ); expectTypirTypes( loxServices.typir, isFunctionType, - "myMethod", - "methodC1", - "methodC2", + 'myMethod', + 'methodC1', + 'methodC2', ...operatorNames, ); }); - test("Class inheritance and the order of type definitions", async () => { + test('Class inheritance and the order of type definitions', async () => { // the "normal" case: 1st super class, 2nd sub class await validateLox( ` @@ -457,12 +457,12 @@ describe("Cyclic type definitions where a Class is declared and already used", ( expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); - test("Class inheritance and the order of type definitions", async () => { + test('Class inheritance and the order of type definitions', async () => { // switching the order of super and sub class works in Langium and in Typir await validateLox( ` @@ -474,14 +474,14 @@ describe("Cyclic type definitions where a Class is declared and already used", ( expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); }); -describe("Test internal validation of Typir for cycles in the class inheritance hierarchy", () => { - test("Three involved classes: 1 -> 2 -> 3 -> 1", async () => { +describe('Test internal validation of Typir for cycles in the class inheritance hierarchy', () => { + test('Three involved classes: 1 -> 2 -> 3 -> 1', async () => { await validateLox( ` class MyClass1 < MyClass3 { } @@ -489,53 +489,53 @@ describe("Test internal validation of Typir for cycles in the class inheritance class MyClass3 < MyClass2 { } `, [ - "Cycles in super-sub-class-relationships are not allowed: MyClass1", - "Cycles in super-sub-class-relationships are not allowed: MyClass2", - "Cycles in super-sub-class-relationships are not allowed: MyClass3", + 'Cycles in super-sub-class-relationships are not allowed: MyClass1', + 'Cycles in super-sub-class-relationships are not allowed: MyClass2', + 'Cycles in super-sub-class-relationships are not allowed: MyClass3', ], ); expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", - "MyClass3", + 'MyClass1', + 'MyClass2', + 'MyClass3', ); }); - test("Two involved classes: 1 -> 2 -> 1", async () => { + test('Two involved classes: 1 -> 2 -> 1', async () => { await validateLox( ` class MyClass1 < MyClass2 { } class MyClass2 < MyClass1 { } `, [ - "Cycles in super-sub-class-relationships are not allowed: MyClass1", - "Cycles in super-sub-class-relationships are not allowed: MyClass2", + 'Cycles in super-sub-class-relationships are not allowed: MyClass1', + 'Cycles in super-sub-class-relationships are not allowed: MyClass2', ], ); expectTypirTypes( loxServices.typir, isClassType, - "MyClass1", - "MyClass2", + 'MyClass1', + 'MyClass2', ); }); - test("One involved class: 1 -> 1", async () => { + test('One involved class: 1 -> 1', async () => { await validateLox( ` class MyClass1 < MyClass1 { } `, - "Cycles in super-sub-class-relationships are not allowed: MyClass1", + 'Cycles in super-sub-class-relationships are not allowed: MyClass1', ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); }); -describe("longer LOX examples with classes regarding ordering", () => { +describe('longer LOX examples with classes regarding ordering', () => { // this test case will work after having the support for cyclic type definitions, since it will solve also issues with topological order of type definitions - test("complete with difficult order of classes", async () => { + test('complete with difficult order of classes', async () => { await validateLox( ` class SuperClass { @@ -577,13 +577,13 @@ describe("longer LOX examples with classes regarding ordering", () => { expectTypirTypes( loxServices.typir, isClassType, - "SuperClass", - "SubClass", - "NestedClass", + 'SuperClass', + 'SubClass', + 'NestedClass', ); }); - test("complete with easy order of classes", async () => { + test('complete with easy order of classes', async () => { await validateLox( ` class SuperClass { @@ -626,9 +626,9 @@ describe("longer LOX examples with classes regarding ordering", () => { expectTypirTypes( loxServices.typir, isClassType, - "SuperClass", - "SubClass", - "NestedClass", + 'SuperClass', + 'SubClass', + 'NestedClass', ); }); }); diff --git a/examples/lox/test/lox-type-checking-functions.test.ts b/examples/lox/test/lox-type-checking-functions.test.ts index a7faf9ec..cbdf3f53 100644 --- a/examples/lox/test/lox-type-checking-functions.test.ts +++ b/examples/lox/test/lox-type-checking-functions.test.ts @@ -10,83 +10,83 @@ import { isFunctionType, isPrimitiveType, isType, -} from "typir"; -import { expectTypirTypes } from "typir/test"; -import { describe, expect, test } from "vitest"; +} from 'typir'; +import { expectTypirTypes } from 'typir/test'; +import { describe, expect, test } from 'vitest'; +import type { LoxProgram } from '../src/language/generated/ast.js'; import { isFunctionDeclaration, isMemberCall, - LoxProgram, -} from "../src/language/generated/ast.js"; +} from '../src/language/generated/ast.js'; import { loxServices, operatorNames, validateLox, -} from "./lox-type-checking-utils.js"; +} from './lox-type-checking-utils.js'; -describe("Test type checking for user-defined functions", () => { - test("function: return value and return type must match", async () => { - await validateLox("fun myFunction1() : boolean { return true; }", 0); +describe('Test type checking for user-defined functions', () => { + test('function: return value and return type must match', async () => { + await validateLox('fun myFunction1() : boolean { return true; }', 0); await validateLox( - "fun myFunction2() : boolean { return 2; }", + 'fun myFunction2() : boolean { return 2; }', "The expression '2' of type 'number' is not usable as return value for the function 'myFunction2' with return type 'boolean'.", ); - await validateLox("fun myFunction3() : number { return 2; }", 0); + await validateLox('fun myFunction3() : number { return 2; }', 0); await validateLox( - "fun myFunction4() : number { return true; }", + 'fun myFunction4() : number { return true; }', "The expression 'true' of type 'boolean' is not usable as return value for the function 'myFunction4' with return type 'number'.", ); expectTypirTypes( loxServices.typir, isFunctionType, - "myFunction1", - "myFunction2", - "myFunction3", - "myFunction4", + 'myFunction1', + 'myFunction2', + 'myFunction3', + 'myFunction4', ...operatorNames, ); }); - test("overloaded function: different return types are not enough", async () => { + test('overloaded function: different return types are not enough', async () => { await validateLox( ` fun myFunction() : boolean { return true; } fun myFunction() : number { return 2; } `, [ - "Declared functions need to be unique (myFunction()).", - "Declared functions need to be unique (myFunction()).", + 'Declared functions need to be unique (myFunction()).', + 'Declared functions need to be unique (myFunction()).', ], ); expectTypirTypes( loxServices.typir, isFunctionType, - "myFunction", - "myFunction", + 'myFunction', + 'myFunction', ...operatorNames, ); // the types are different nevertheless! }); - test("overloaded function: different parameter names are not enough", async () => { + test('overloaded function: different parameter names are not enough', async () => { await validateLox( ` fun myFunction(input: boolean) : boolean { return true; } fun myFunction(other: boolean) : boolean { return true; } `, [ - "Declared functions need to be unique (myFunction(boolean)).", - "Declared functions need to be unique (myFunction(boolean)).", + 'Declared functions need to be unique (myFunction(boolean)).', + 'Declared functions need to be unique (myFunction(boolean)).', ], ); expectTypirTypes( loxServices.typir, isFunctionType, - "myFunction", + 'myFunction', ...operatorNames, ); // but both functions have the same type! }); - test("overloaded function: but different parameter types are fine", async () => { + test('overloaded function: but different parameter types are fine', async () => { await validateLox( ` fun myFunction(input: boolean) : boolean { return true; } @@ -97,13 +97,13 @@ describe("Test type checking for user-defined functions", () => { expectTypirTypes( loxServices.typir, isFunctionType, - "myFunction", - "myFunction", + 'myFunction', + 'myFunction', ...operatorNames, ); }); - test("overloaded function: check correct type inference and cross-references", async () => { + test('overloaded function: check correct type inference and cross-references', async () => { const rootNode = ( await validateLox( ` @@ -118,8 +118,8 @@ describe("Test type checking for user-defined functions", () => { expectTypirTypes( loxServices.typir, isFunctionType, - "myFunction", - "myFunction", + 'myFunction', + 'myFunction', ...operatorNames, ); @@ -132,12 +132,12 @@ describe("Test type checking for user-defined functions", () => { assertTrue(isMemberCall(call1Node)); const method1 = call1Node.element?.ref; assertTrue(isFunctionDeclaration(method1)); - expect(method1.returnType.primitive).toBe("number"); + expect(method1.returnType.primitive).toBe('number'); // check type inference const call1Type = loxServices.typir.Inference.inferType(call1Node); expect(isType(call1Type)).toBeTruthy(); assertTypirType(call1Type, isPrimitiveType); - expect(call1Type.getName()).toBe("number"); + expect(call1Type.getName()).toBe('number'); // Call 2 should be boolean const call2Node = rootNode.elements[3]; @@ -145,11 +145,11 @@ describe("Test type checking for user-defined functions", () => { assertTrue(isMemberCall(call2Node)); const method2 = call2Node.element?.ref; assertTrue(isFunctionDeclaration(method2)); - expect(method2.returnType.primitive).toBe("boolean"); + expect(method2.returnType.primitive).toBe('boolean'); // check type inference const call2Type = loxServices.typir.Inference.inferType(call2Node); expect(isType(call2Type)).toBeTruthy(); assertTypirType(call2Type, isPrimitiveType); - expect(call2Type.getName()).toBe("boolean"); + expect(call2Type.getName()).toBe('boolean'); }); }); diff --git a/examples/lox/test/lox-type-checking-method.test.ts b/examples/lox/test/lox-type-checking-method.test.ts index 59a10117..aa49076f 100644 --- a/examples/lox/test/lox-type-checking-method.test.ts +++ b/examples/lox/test/lox-type-checking-method.test.ts @@ -11,22 +11,19 @@ import { isFunctionType, isPrimitiveType, isType, -} from "typir"; -import { expectTypirTypes } from "typir/test"; -import { describe, expect, test } from "vitest"; -import { - isMemberCall, - isMethodMember, - LoxProgram, -} from "../src/language/generated/ast.js"; +} from 'typir'; +import { expectTypirTypes } from 'typir/test'; +import { describe, expect, test } from 'vitest'; +import type { LoxProgram } from '../src/language/generated/ast.js'; +import { isMemberCall, isMethodMember } from '../src/language/generated/ast.js'; import { loxServices, operatorNames, validateLox, -} from "./lox-type-checking-utils.js"; +} from './lox-type-checking-utils.js'; -describe("Test type checking for methods of classes", () => { - test("Class methods: OK", async () => { +describe('Test type checking for methods of classes', () => { + test('Class methods: OK', async () => { await validateLox( ` class MyClass1 { @@ -39,10 +36,10 @@ describe("Test type checking for methods of classes", () => { `, [], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Class methods: wrong return value", async () => { + test('Class methods: wrong return value', async () => { await validateLox( ` class MyClass1 { @@ -55,10 +52,10 @@ describe("Test type checking for methods of classes", () => { `, 1, ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Class methods: method return type does not fit to variable type", async () => { + test('Class methods: method return type does not fit to variable type', async () => { await validateLox( ` class MyClass1 { @@ -71,10 +68,10 @@ describe("Test type checking for methods of classes", () => { `, 1, ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Class methods: value for input parameter does not fit to the type of the input parameter", async () => { + test('Class methods: value for input parameter does not fit to the type of the input parameter', async () => { await validateLox( ` class MyClass1 { @@ -87,10 +84,10 @@ describe("Test type checking for methods of classes", () => { `, 1, ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); - test("Class methods: methods are not distinguishable", async () => { + test('Class methods: methods are not distinguishable', async () => { await validateLox( ` class MyClass1 { @@ -104,15 +101,15 @@ describe("Test type checking for methods of classes", () => { `, [ // both methods need to be marked: - "Declared methods need to be unique (class-MyClass1.method1(number)).", - "Declared methods need to be unique (class-MyClass1.method1(number)).", + 'Declared methods need to be unique (class-MyClass1.method1(number)).', + 'Declared methods need to be unique (class-MyClass1.method1(number)).', ], ); - expectTypirTypes(loxServices.typir, isClassType, "MyClass1"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); }); -describe("Test overloaded methods", () => { +describe('Test overloaded methods', () => { const methodDeclaration = ` class MyClass { method1(input: number): number { @@ -124,7 +121,7 @@ describe("Test overloaded methods", () => { } `; - test("Calls with correct arguments", async () => { + test('Calls with correct arguments', async () => { const rootNode = ( await validateLox( `${methodDeclaration} @@ -135,12 +132,12 @@ describe("Test overloaded methods", () => { [], ) ).parseResult.value as LoxProgram; - expectTypirTypes(loxServices.typir, isClassType, "MyClass"); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); expectTypirTypes( loxServices.typir, isFunctionType, - "method1", - "method1", + 'method1', + 'method1', ...operatorNames, ); @@ -153,12 +150,12 @@ describe("Test overloaded methods", () => { assertTrue(isMemberCall(call1Node)); const method1 = call1Node.element?.ref; assertTrue(isMethodMember(method1)); - expect(method1.returnType.primitive).toBe("number"); + expect(method1.returnType.primitive).toBe('number'); // check type inference const call1Type = loxServices.typir.Inference.inferType(call1Node); expect(isType(call1Type)).toBeTruthy(); assertTypirType(call1Type, isPrimitiveType); - expect(call1Type.getName()).toBe("number"); + expect(call1Type.getName()).toBe('number'); // Call 2 should be boolean const call2Node = rootNode.elements[3]; @@ -166,15 +163,15 @@ describe("Test overloaded methods", () => { assertTrue(isMemberCall(call2Node)); const method2 = call2Node.element?.ref; assertTrue(isMethodMember(method2)); - expect(method2.returnType.primitive).toBe("boolean"); + expect(method2.returnType.primitive).toBe('boolean'); // check type inference const call2Type = loxServices.typir.Inference.inferType(call2Node); expect(isType(call2Type)).toBeTruthy(); assertTypirType(call2Type, isPrimitiveType); - expect(call2Type.getName()).toBe("boolean"); + expect(call2Type.getName()).toBe('boolean'); }); - test("Call with wrong argument", async () => { + test('Call with wrong argument', async () => { await validateLox( `${methodDeclaration} var v = MyClass(); diff --git a/examples/lox/test/lox-type-checking-operators.test.ts b/examples/lox/test/lox-type-checking-operators.test.ts index b1b9c537..627c122a 100644 --- a/examples/lox/test/lox-type-checking-operators.test.ts +++ b/examples/lox/test/lox-type-checking-operators.test.ts @@ -4,55 +4,55 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { validateLox } from "./lox-type-checking-utils.js"; +import { describe, test } from 'vitest'; +import { validateLox } from './lox-type-checking-utils.js'; -describe("Test type checking for operators", () => { - test("binary operators", async () => { - await validateLox("var myResult: number = 2 + 3;", 0); - await validateLox("var myResult: number = 2 - 3;", 0); - await validateLox("var myResult: number = 2 * 3;", 0); - await validateLox("var myResult: number = 2 / 3;", 0); +describe('Test type checking for operators', () => { + test('binary operators', async () => { + await validateLox('var myResult: number = 2 + 3;', 0); + await validateLox('var myResult: number = 2 - 3;', 0); + await validateLox('var myResult: number = 2 * 3;', 0); + await validateLox('var myResult: number = 2 / 3;', 0); - await validateLox("var myResult: boolean = 2 < 3;", 0); - await validateLox("var myResult: boolean = 2 <= 3;", 0); - await validateLox("var myResult: boolean = 2 > 3;", 0); - await validateLox("var myResult: boolean = 2 >= 3;", 0); + await validateLox('var myResult: boolean = 2 < 3;', 0); + await validateLox('var myResult: boolean = 2 <= 3;', 0); + await validateLox('var myResult: boolean = 2 > 3;', 0); + await validateLox('var myResult: boolean = 2 >= 3;', 0); - await validateLox("var myResult: boolean = true and false;", 0); - await validateLox("var myResult: boolean = true or false;", 0); + await validateLox('var myResult: boolean = true and false;', 0); + await validateLox('var myResult: boolean = true or false;', 0); - await validateLox("var myResult: boolean = 2 == 3;", 0); - await validateLox("var myResult: boolean = 2 != 3;", 0); - await validateLox("var myResult: boolean = true == false;", 0); - await validateLox("var myResult: boolean = true != false;", 0); + await validateLox('var myResult: boolean = 2 == 3;', 0); + await validateLox('var myResult: boolean = 2 != 3;', 0); + await validateLox('var myResult: boolean = true == false;', 0); + await validateLox('var myResult: boolean = true != false;', 0); await validateLox( - "var myResult: boolean = true == 3;", + 'var myResult: boolean = true == 3;', 0, "This comparison will always return 'false' as 'true' and '3' have the different types 'boolean' and 'number'.", ); await validateLox( - "var myResult: boolean = 2 != false;", + 'var myResult: boolean = 2 != false;', 0, "This comparison will always return 'true' as '2' and 'false' have the different types 'number' and 'boolean'.", ); }); - test("unary operator: !", async () => { - await validateLox("var myResult: boolean = !true;", 0); - await validateLox("var myResult: boolean = !!true;", 0); - await validateLox("var myResult: boolean = !!!true;", 0); + test('unary operator: !', async () => { + await validateLox('var myResult: boolean = !true;', 0); + await validateLox('var myResult: boolean = !!true;', 0); + await validateLox('var myResult: boolean = !!!true;', 0); }); - test("unary operator: -", async () => { - await validateLox("var myResult: number = -2;", 0); - await validateLox("var myResult: number = --2;", 0); - await validateLox("var myResult: number = ---2;", 0); + test('unary operator: -', async () => { + await validateLox('var myResult: number = -2;', 0); + await validateLox('var myResult: number = --2;', 0); + await validateLox('var myResult: number = ---2;', 0); }); test('overloaded operator "+"', async () => { - await validateLox("var myResult: number = 1 + 2;", 0); + await validateLox('var myResult: number = 1 + 2;', 0); await validateLox('var myResult: string = "a" + "b";', 0); await validateLox('var myResult: string = "a" + 2;', 0); await validateLox('var myResult: string = 1 + "b";', 0); @@ -60,28 +60,28 @@ describe("Test type checking for operators", () => { await validateLox('var myResult: string = "a" + false;', 1); }); - test("use overloaded operators: +", async () => { - await validateLox("var myVar : number = 2 + 3;", 0, 0); + test('use overloaded operators: +', async () => { + await validateLox('var myVar : number = 2 + 3;', 0, 0); await validateLox('var myVar : string = "a" + "b";', 0, 0); await validateLox('var myVar : string = "a" + 3;', 0, 0); await validateLox('var myVar : string = 2 + "b";', 0, 0); }); - test("use overloaded operators: ==", async () => { - await validateLox("var myVar : boolean = true == false;", 0, 0); - await validateLox("var myVar : boolean = 2 == 3;", 0, 0); - await validateLox("var myVar : boolean = true == 3;", 0, 1); - await validateLox("var myVar : boolean = 2 == false;", 0, 1); + test('use overloaded operators: ==', async () => { + await validateLox('var myVar : boolean = true == false;', 0, 0); + await validateLox('var myVar : boolean = 2 == 3;', 0, 0); + await validateLox('var myVar : boolean = true == 3;', 0, 1); + await validateLox('var myVar : boolean = 2 == false;', 0, 1); }); test('Only a single problem with the inner expression, since the type of "*" is always number!', async () => { - await validateLox("var myVar : number = 2 * (2 * false);", [ + await validateLox('var myVar : number = 2 * (2 * false);', [ "While validating the AstNode '(2 * false)', this error is found: The given operands for the call of '*' don't match.", ]); }); test('Two issues in nested expressions, since "*" expects always numbers, while "and" returns always booleans!', async () => { - await validateLox("var myVar : number = 2 * (2 and false);", [ + await validateLox('var myVar : number = 2 * (2 and false);', [ // this is obvious: left and right need to have the same type "While validating the AstNode '(2 and false)', this error is found: The given operands for the call of 'and' don't match.", // '*' supports only numbers for left and right, but the right operand is always boolean as result of the 'and' operator diff --git a/examples/lox/test/lox-type-checking-statements.test.ts b/examples/lox/test/lox-type-checking-statements.test.ts index f8968756..0aa6fd56 100644 --- a/examples/lox/test/lox-type-checking-statements.test.ts +++ b/examples/lox/test/lox-type-checking-statements.test.ts @@ -4,51 +4,51 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { validateLox } from "./lox-type-checking-utils.js"; +import { describe, test } from 'vitest'; +import { validateLox } from './lox-type-checking-utils.js'; -describe("Test type checking for statements and variables in LOX", () => { - test("multiple nested and", async () => { - await validateLox("var myResult: boolean = true and false;", 0); +describe('Test type checking for statements and variables in LOX', () => { + test('multiple nested and', async () => { + await validateLox('var myResult: boolean = true and false;', 0); await validateLox( - "var myResult: boolean = true and false and true;", + 'var myResult: boolean = true and false and true;', 0, ); }); - test("number assignments", async () => { - await validateLox("var myResult: number = 2;", 0); - await validateLox("var myResult: number = 2 * 3;", 0); - await validateLox("var myResult: number = 2 < 3;", 1); - await validateLox("var myResult: number = true;", 1); + test('number assignments', async () => { + await validateLox('var myResult: number = 2;', 0); + await validateLox('var myResult: number = 2 * 3;', 0); + await validateLox('var myResult: number = 2 < 3;', 1); + await validateLox('var myResult: number = true;', 1); }); - test("boolean assignments", async () => { - await validateLox("var myResult: boolean = true;", 0); - await validateLox("var myResult: boolean = 2;", 1); - await validateLox("var myResult: boolean = 2 * 3;", 1); - await validateLox("var myResult: boolean = 2 < 3;", 0); + test('boolean assignments', async () => { + await validateLox('var myResult: boolean = true;', 0); + await validateLox('var myResult: boolean = 2;', 1); + await validateLox('var myResult: boolean = 2 * 3;', 1); + await validateLox('var myResult: boolean = 2 < 3;', 0); }); - test("statement assignments", async () => { - await validateLox("var myResult: boolean; myResult = true;", 0); - await validateLox("var myResult: boolean; myResult = 2;", 1); - await validateLox("var myResult: boolean; myResult = 2 * 3;", 1); - await validateLox("var myResult: boolean; myResult = 2 < 3;", 0); + test('statement assignments', async () => { + await validateLox('var myResult: boolean; myResult = true;', 0); + await validateLox('var myResult: boolean; myResult = 2;', 1); + await validateLox('var myResult: boolean; myResult = 2 * 3;', 1); + await validateLox('var myResult: boolean; myResult = 2 < 3;', 0); }); - test("boolean in conditions", async () => { - await validateLox("if ( true ) {}", 0); - await validateLox("if ( 3 ) {}", 1); + test('boolean in conditions', async () => { + await validateLox('if ( true ) {}', 0); + await validateLox('if ( 3 ) {}', 1); }); - test("variable declarations", async () => { - await validateLox("var myVar : boolean;", 0); - await validateLox("var myVar : number;", 0); - await validateLox("var myVar : void;", 1); + test('variable declarations', async () => { + await validateLox('var myVar : boolean;', 0); + await validateLox('var myVar : number;', 0); + await validateLox('var myVar : void;', 1); }); - test("Variables without explicit type: assignment", async () => { + test('Variables without explicit type: assignment', async () => { await validateLox( ` var min = 14; @@ -59,7 +59,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: assign expression to var without type", async () => { + test('Variables without explicit type: assign expression to var without type', async () => { await validateLox( ` var min = 14; @@ -70,7 +70,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: assign expression to var with type", async () => { + test('Variables without explicit type: assign expression to var with type', async () => { await validateLox( ` var min = 14; @@ -81,7 +81,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: assign var again with expression of overloaded operator +", async () => { + test('Variables without explicit type: assign var again with expression of overloaded operator +', async () => { await validateLox( ` var min = 14; @@ -92,7 +92,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: assign var again with expression of overloaded operator -", async () => { + test('Variables without explicit type: assign var again with expression of overloaded operator -', async () => { await validateLox( ` var min = 14; @@ -103,7 +103,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: assign var again with expression of not overloaded operator *", async () => { + test('Variables without explicit type: assign var again with expression of not overloaded operator *', async () => { await validateLox( ` var min = 14; @@ -114,7 +114,7 @@ describe("Test type checking for statements and variables in LOX", () => { ); }); - test("Variables without explicit type: used in function", async () => { + test('Variables without explicit type: used in function', async () => { await validateLox( ` var min = 14; diff --git a/examples/lox/test/lox-type-checking-utils.ts b/examples/lox/test/lox-type-checking-utils.ts index 37cad50b..5f42862d 100644 --- a/examples/lox/test/lox-type-checking-utils.ts +++ b/examples/lox/test/lox-type-checking-utils.ts @@ -4,36 +4,37 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EmptyFileSystem, LangiumDocument } from "langium"; -import { parseDocument } from "langium/test"; -import { isClassType, isFunctionType } from "typir"; -import { deleteAllDocuments } from "typir-langium"; -import { compareValidationIssuesStrict, expectTypirTypes } from "typir/test"; -import { afterEach, expect } from "vitest"; -import type { Diagnostic } from "vscode-languageserver-types"; -import { DiagnosticSeverity } from "vscode-languageserver-types"; -import { createLoxServices } from "../src/language/lox-module.js"; +import type { LangiumDocument } from 'langium'; +import { EmptyFileSystem } from 'langium'; +import { parseDocument } from 'langium/test'; +import { isClassType, isFunctionType } from 'typir'; +import { deleteAllDocuments } from 'typir-langium'; +import { compareValidationIssuesStrict, expectTypirTypes } from 'typir/test'; +import { afterEach, expect } from 'vitest'; +import type { Diagnostic } from 'vscode-languageserver-types'; +import { DiagnosticSeverity } from 'vscode-languageserver-types'; +import { createLoxServices } from '../src/language/lox-module.js'; export const loxServices = createLoxServices(EmptyFileSystem).Lox; export const operatorNames = [ - "-", - "*", - "/", - "+", - "+", - "+", - "+", - "<", - "<=", - ">", - ">=", - "and", - "or", - "==", - "!=", - "=", - "!", - "-", + '-', + '*', + '/', + '+', + '+', + '+', + '+', + '<', + '<=', + '>', + '>=', + 'and', + 'or', + '==', + '!=', + '=', + '!', + '-', ]; afterEach(async () => { @@ -73,10 +74,10 @@ function checkIssues( diagnosticsErrors: string[], errors: number | string | string[], ): void { - const msgError = diagnosticsErrors.join("\n"); - if (typeof errors === "number") { + const msgError = diagnosticsErrors.join('\n'); + if (typeof errors === 'number') { expect(diagnosticsErrors, msgError).toHaveLength(errors); - } else if (typeof errors === "string") { + } else if (typeof errors === 'string') { expect(diagnosticsErrors, msgError).toHaveLength(1); expect(diagnosticsErrors[0], msgError).includes(errors); } else { diff --git a/examples/ox/src/cli/cli-util.ts b/examples/ox/src/cli/cli-util.ts index 84036c69..f8c581e2 100644 --- a/examples/ox/src/cli/cli-util.ts +++ b/examples/ox/src/cli/cli-util.ts @@ -4,12 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { AstNode, LangiumCoreServices, LangiumDocument } from "langium"; -import chalk from "chalk"; -import * as path from "node:path"; -import * as fs from "node:fs"; -import { URI } from "langium"; -import { LangiumServices } from "langium/lsp"; +import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium'; +import chalk from 'chalk'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import { URI } from 'langium'; +import type { LangiumServices } from 'langium/lsp'; export async function extractDocument( fileName: string, @@ -42,7 +42,7 @@ export async function extractDocument( (e) => e.severity === 1, ); if (validationErrors.length > 0) { - console.error(chalk.red("There are validation errors:")); + console.error(chalk.red('There are validation errors:')); for (const validationError of validationErrors) { console.error( chalk.red( @@ -74,10 +74,10 @@ export function extractDestinationAndName( ): FilePathData { filePath = path .basename(filePath, path.extname(filePath)) - .replace(/[.-]/g, ""); + .replace(/[.-]/g, ''); return { destination: - destination ?? path.join(path.dirname(filePath), "generated"), + destination ?? path.join(path.dirname(filePath), 'generated'), name: path.basename(filePath), }; } diff --git a/examples/ox/src/cli/main.ts b/examples/ox/src/cli/main.ts index 2f6c83ad..79c29eac 100644 --- a/examples/ox/src/cli/main.ts +++ b/examples/ox/src/cli/main.ts @@ -4,18 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Command } from "commander"; -import { OxLanguageMetaData } from "../language/generated/module.js"; -import { extractDestinationAndName } from "./cli-util.js"; -import * as url from "node:url"; -import * as fsp from "node:fs/promises"; -import * as path from "node:path"; -import * as fs from "node:fs"; +import { Command } from 'commander'; +import { OxLanguageMetaData } from '../language/generated/module.js'; +import { extractDestinationAndName } from './cli-util.js'; +import * as url from 'node:url'; +import * as fsp from 'node:fs/promises'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); -const packagePath = path.resolve(__dirname, "..", "..", "package.json"); -const packageContent = await fsp.readFile(packagePath, "utf-8"); +const packagePath = path.resolve(__dirname, '..', '..', 'package.json'); +const packageContent = await fsp.readFile(packagePath, 'utf-8'); export const generateAction = async ( fileName: string, @@ -34,23 +34,23 @@ export type GenerateOptions = { destination?: string; }; -export default function (): void { +export default function(): void { const program = new Command(); program.version(JSON.parse(packageContent).version); - const fileExtensions = OxLanguageMetaData.fileExtensions.join(", "); + const fileExtensions = OxLanguageMetaData.fileExtensions.join(', '); program - .command("generate") + .command('generate') .argument( - "", + '', `source file (possible file extensions: ${fileExtensions})`, ) .option( - "-d, --destination ", - "destination directory of generating", + '-d, --destination ', + 'destination directory of generating', ) - .description("generates from the source file") + .description('generates from the source file') .action(generateAction); program.parse(process.argv); diff --git a/examples/ox/src/extension/main.ts b/examples/ox/src/extension/main.ts index 9d9ad2d7..b990b881 100644 --- a/examples/ox/src/extension/main.ts +++ b/examples/ox/src/extension/main.ts @@ -7,10 +7,10 @@ import type { LanguageClientOptions, ServerOptions, -} from "vscode-languageclient/node.js"; -import * as vscode from "vscode"; -import * as path from "node:path"; -import { LanguageClient, TransportKind } from "vscode-languageclient/node.js"; +} from 'vscode-languageclient/node.js'; +import * as vscode from 'vscode'; +import * as path from 'node:path'; +import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js'; let client: LanguageClient; @@ -29,15 +29,15 @@ export function deactivate(): Thenable | undefined { function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { const serverModule = context.asAbsolutePath( - path.join("out", "language", "main.cjs"), + path.join('out', 'language', 'main.cjs'), ); // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached. const debugOptions = { execArgv: [ - "--nolazy", - `--inspect${process.env.DEBUG_BREAK ? "-brk" : ""}=${process.env.DEBUG_SOCKET || "6009"}`, + '--nolazy', + `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`, ], }; @@ -53,12 +53,12 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { }; const fileSystemWatcher = - vscode.workspace.createFileSystemWatcher("**/*.ox"); + vscode.workspace.createFileSystemWatcher('**/*.ox'); context.subscriptions.push(fileSystemWatcher); // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: "file", language: "ox" }], + documentSelector: [{ scheme: 'file', language: 'ox' }], synchronize: { // Notify the server about file changes to files contained in the workspace fileEvents: fileSystemWatcher, @@ -66,7 +66,7 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { }; // Create the language client and start the client. - const client = new LanguageClient("ox", "Ox", serverOptions, clientOptions); + const client = new LanguageClient('ox', 'Ox', serverOptions, clientOptions); // Start the client. This will also launch the server client.start(); diff --git a/examples/ox/src/language/main.ts b/examples/ox/src/language/main.ts index 5632be71..c247692b 100644 --- a/examples/ox/src/language/main.ts +++ b/examples/ox/src/language/main.ts @@ -4,13 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { startLanguageServer } from "langium/lsp"; -import { NodeFileSystem } from "langium/node"; +import { startLanguageServer } from 'langium/lsp'; +import { NodeFileSystem } from 'langium/node'; import { createConnection, ProposedFeatures, -} from "vscode-languageserver/node.js"; -import { createOxServices } from "./ox-module.js"; +} from 'vscode-languageserver/node.js'; +import { createOxServices } from './ox-module.js'; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); diff --git a/examples/ox/src/language/ox-module.ts b/examples/ox/src/language/ox-module.ts index 07905f0e..723982db 100644 --- a/examples/ox/src/language/ox-module.ts +++ b/examples/ox/src/language/ox-module.ts @@ -4,27 +4,28 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { LangiumSharedCoreServices, Module, inject } from "langium"; -import { +import type { LangiumSharedCoreServices, Module } from 'langium'; +import { inject } from 'langium'; +import type { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, PartialLangiumServices, - createDefaultModule, - createDefaultSharedModule, -} from "langium/lsp"; +} from 'langium/lsp'; +import { createDefaultModule, createDefaultSharedModule } from 'langium/lsp'; +import type { TypirLangiumServices } from 'typir-langium'; import { - TypirLangiumServices, createTypirLangiumServices, initializeLangiumTypirServices, -} from "typir-langium"; -import { OxAstType, reflection } from "./generated/ast.js"; +} from 'typir-langium'; +import type { OxAstType } from './generated/ast.js'; +import { reflection } from './generated/ast.js'; import { OxGeneratedModule, OxGeneratedSharedModule, -} from "./generated/module.js"; -import { OxTypeSystem } from "./ox-type-checking.js"; -import { OxValidator, registerValidationChecks } from "./ox-validator.js"; +} from './generated/module.js'; +import { OxTypeSystem } from './ox-type-checking.js'; +import { OxValidator, registerValidationChecks } from './ox-validator.js'; /** * Declaration of custom services - add your own service classes here. diff --git a/examples/ox/src/language/ox-type-checking.ts b/examples/ox/src/language/ox-type-checking.ts index a0bb84e7..b98163f4 100644 --- a/examples/ox/src/language/ox-type-checking.ts +++ b/examples/ox/src/language/ox-type-checking.ts @@ -4,31 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, assertUnreachable } from "langium"; -import { +import type { AstNode } from 'langium'; +import { AstUtils, assertUnreachable } from 'langium'; +import type { CreateParameterDetails, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand, - InferenceRuleNotApplicable, - NO_PARAMETER_NAME, TypirServices, ValidationProblemAcceptor, -} from "typir"; -import { +} from 'typir'; +import { InferenceRuleNotApplicable, NO_PARAMETER_NAME } from 'typir'; +import type { LangiumTypeSystemDefinition, TypirLangiumServices, -} from "typir-langium"; -import { +} from 'typir-langium'; +import type { BinaryExpression, ForStatement, - FunctionDeclaration, IfStatement, - MemberCall, - NumberLiteral, OxAstType, - TypeReference, UnaryExpression, WhileStatement, +} from './generated/ast.js'; +import { + FunctionDeclaration, + MemberCall, + NumberLiteral, + TypeReference, isBinaryExpression, isBooleanLiteral, isFunctionDeclaration, @@ -36,37 +38,37 @@ import { isTypeReference, isUnaryExpression, isVariableDeclaration, -} from "./generated/ast.js"; +} from './generated/ast.js'; export class OxTypeSystem implements LangiumTypeSystemDefinition { onInitialize(typir: TypirLangiumServices): void { // define primitive types // typeBool, typeNumber and typeVoid are specific types for OX, ... const typeBool = typir.factory.Primitives.create({ - primitiveName: "boolean", + primitiveName: 'boolean', }) .inferenceRule({ filter: isBooleanLiteral }) .inferenceRule({ filter: isTypeReference, - matching: (node) => node.primitive === "boolean", + matching: (node) => node.primitive === 'boolean', }) .finish(); // ... but their primitive kind is provided/preset by Typir const typeNumber = typir.factory.Primitives.create({ - primitiveName: "number", + primitiveName: 'number', }) .inferenceRule({ languageKey: NumberLiteral }) .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "number", + matching: (node: TypeReference) => node.primitive === 'number', }) .finish(); const typeVoid = typir.factory.Primitives.create({ - primitiveName: "void", + primitiveName: 'void', }) .inferenceRule({ languageKey: TypeReference, - matching: (node: TypeReference) => node.primitive === "void", + matching: (node: TypeReference) => node.primitive === 'void', }) .finish(); @@ -97,7 +99,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { // define operators // binary operators: numbers => number - for (const operator of ["+", "-", "*", "/"]) { + for (const operator of ['+', '-', '*', '/']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -110,7 +112,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { .finish(); } // binary operators: numbers => boolean - for (const operator of ["<", "<=", ">", ">="]) { + for (const operator of ['<', '<=', '>', '>=']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -123,7 +125,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { .finish(); } // binary operators: booleans => boolean - for (const operator of ["and", "or"]) { + for (const operator of ['and', 'or']) { typir.factory.Operators.createBinary({ name: operator, signature: { @@ -136,7 +138,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { .finish(); } // ==, != for booleans and numbers - for (const operator of ["==", "!="]) { + for (const operator of ['==', '!=']) { typir.factory.Operators.createBinary({ name: operator, signatures: [ @@ -150,13 +152,13 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { // unary operators typir.factory.Operators.createUnary({ - name: "!", + name: '!', signature: { operand: typeBool, return: typeBool }, }) .inferenceRule(unaryInferenceRule) .finish(); typir.factory.Operators.createUnary({ - name: "-", + name: '-', signature: { operand: typeNumber, return: typeNumber }, }) .inferenceRule(unaryInferenceRule) @@ -217,7 +219,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { accept, (actual, expected) => ({ message: `The expression '${node.value.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.varRef.ref!.name}' with type '${expected.name}'.`, - languageProperty: "value", + languageProperty: 'value', }), ); } @@ -231,7 +233,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { ); if ( functionDeclaration && - functionDeclaration.returnType.primitive !== "void" && + functionDeclaration.returnType.primitive !== 'void' && node.value ) { // the return value must fit to the return type of the function @@ -241,7 +243,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { accept, () => ({ message: `The expression '${node.value!.$cstNode?.text}' is not usable as return value for the function '${functionDeclaration.name}'.`, - languageProperty: "value", + languageProperty: 'value', }), ); } @@ -254,7 +256,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { () => ({ message: "Variables can't be declared with the type 'void'.", - languageProperty: "type", + languageProperty: 'type', }), ); typir.validation.Constraints.ensureNodeIsAssignable( @@ -263,7 +265,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { accept, (actual, expected) => ({ message: `The initialization expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.name}' with type '${expected.name}'.`, - languageProperty: "value", + languageProperty: 'value', }), ); }, @@ -280,7 +282,7 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { accept, () => ({ message: "Conditions need to be evaluated to 'boolean'.", - languageProperty: "condition", + languageProperty: 'condition', }), ); } diff --git a/examples/ox/src/language/ox-validator.ts b/examples/ox/src/language/ox-validator.ts index 0433af56..3232d069 100644 --- a/examples/ox/src/language/ox-validator.ts +++ b/examples/ox/src/language/ox-validator.ts @@ -9,18 +9,20 @@ import { MultiMap, type ValidationAcceptor, type ValidationChecks, -} from "langium"; -import { +} from 'langium'; +import type { FunctionDeclaration, - isFunctionDeclaration, - isVariableDeclaration, OxElement, OxProgram, VariableDeclaration, +} from './generated/ast.js'; +import { + isFunctionDeclaration, + isVariableDeclaration, type OxAstType, type ReturnStatement, -} from "./generated/ast.js"; -import type { OxServices } from "./ox-module.js"; +} from './generated/ast.js'; +import type { OxServices } from './ox-module.js'; /** * Register custom validation checks. @@ -53,13 +55,13 @@ export class OxValidator { isFunctionDeclaration, ); if (functionDeclaration) { - if (functionDeclaration.returnType.primitive === "void") { + if (functionDeclaration.returnType.primitive === 'void') { // no return type if (node.value) { accept( - "error", + 'error', `The function '${functionDeclaration.name}' has 'void' as return type. Therefore, this return statement must return no value.`, - { node, property: "value" }, + { node, property: 'value' }, ); } else { // no value => everything is fine @@ -71,7 +73,7 @@ export class OxValidator { } else { // missing return value accept( - "error", + 'error', `The function '${functionDeclaration.name}' has '${functionDeclaration.returnType.primitive}' as return type. Therefore, this return statement must return value.`, { node }, ); @@ -100,11 +102,11 @@ export class OxValidator { if (vars.length >= 2) { for (const v of vars) { accept( - "error", - "Variables need to have unique names: " + name, + 'error', + 'Variables need to have unique names: ' + name, { node: v, - property: "name", + property: 'name', }, ); } @@ -128,11 +130,11 @@ export class OxValidator { if (declarations.length >= 2) { for (const f of declarations) { accept( - "error", - "Functions need to have unique names: " + name, + 'error', + 'Functions need to have unique names: ' + name, { node: f, - property: "name", + property: 'name', }, ); } diff --git a/examples/ox/test/ox-type-checking-functions.test.ts b/examples/ox/test/ox-type-checking-functions.test.ts index 9f12a34d..7cfdf9f4 100644 --- a/examples/ox/test/ox-type-checking-functions.test.ts +++ b/examples/ox/test/ox-type-checking-functions.test.ts @@ -4,36 +4,36 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { validateOx } from "./ox-type-checking-utils.js"; +import { describe, test } from 'vitest'; +import { validateOx } from './ox-type-checking-utils.js'; -describe("Test type checking for statements and variables in OX", () => { - test("function: return value and return type", async () => { - await validateOx("fun myFunction1() : boolean { return true; }", 0); - await validateOx("fun myFunction2() : boolean { return 2; }", 1); - await validateOx("fun myFunction3() : number { return 2; }", 0); - await validateOx("fun myFunction4() : number { return true; }", 1); +describe('Test type checking for statements and variables in OX', () => { + test('function: return value and return type', async () => { + await validateOx('fun myFunction1() : boolean { return true; }', 0); + await validateOx('fun myFunction2() : boolean { return 2; }', 1); + await validateOx('fun myFunction3() : number { return 2; }', 0); + await validateOx('fun myFunction4() : number { return true; }', 1); }); - test("function: the same function name twice (in the same file) is not allowed in Typir", async () => { + test('function: the same function name twice (in the same file) is not allowed in Typir', async () => { await validateOx( ` fun myFunction() : boolean { return true; } fun myFunction() : boolean { return false; } `, [ - "Functions need to have unique names", - "Functions need to have unique names", + 'Functions need to have unique names', + 'Functions need to have unique names', ], ); }); // TODO this test case needs to be investigated in more detail test.todo( - "function: the same function name twice (even in different files) is not allowed in Typir", + 'function: the same function name twice (even in different files) is not allowed in Typir', async () => { - await validateOx("fun myFunction() : boolean { return true; }", 0); - await validateOx("fun myFunction() : boolean { return false; }", 2); // now, both functions should be marked as "duplicate" + await validateOx('fun myFunction() : boolean { return true; }', 0); + await validateOx('fun myFunction() : boolean { return false; }', 2); // now, both functions should be marked as "duplicate" }, ); }); diff --git a/examples/ox/test/ox-type-checking-operators.test.ts b/examples/ox/test/ox-type-checking-operators.test.ts index 51a1c8fc..9fa2b6ff 100644 --- a/examples/ox/test/ox-type-checking-operators.test.ts +++ b/examples/ox/test/ox-type-checking-operators.test.ts @@ -4,60 +4,60 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { validateOx } from "./ox-type-checking-utils.js"; +import { describe, test } from 'vitest'; +import { validateOx } from './ox-type-checking-utils.js'; -describe("Test type checking for statements and variables in OX", () => { - test("binary operators", async () => { - await validateOx("var myResult: number = 2 + 3;", 0); - await validateOx("var myResult: number = 2 - 3;", 0); - await validateOx("var myResult: number = 2 * 3;", 0); - await validateOx("var myResult: number = 2 / 3;", 0); +describe('Test type checking for statements and variables in OX', () => { + test('binary operators', async () => { + await validateOx('var myResult: number = 2 + 3;', 0); + await validateOx('var myResult: number = 2 - 3;', 0); + await validateOx('var myResult: number = 2 * 3;', 0); + await validateOx('var myResult: number = 2 / 3;', 0); - await validateOx("var myResult: boolean = 2 < 3;", 0); - await validateOx("var myResult: boolean = 2 <= 3;", 0); - await validateOx("var myResult: boolean = 2 > 3;", 0); - await validateOx("var myResult: boolean = 2 >= 3;", 0); + await validateOx('var myResult: boolean = 2 < 3;', 0); + await validateOx('var myResult: boolean = 2 <= 3;', 0); + await validateOx('var myResult: boolean = 2 > 3;', 0); + await validateOx('var myResult: boolean = 2 >= 3;', 0); - await validateOx("var myResult: boolean = true and false;", 0); - await validateOx("var myResult: boolean = true or false;", 0); + await validateOx('var myResult: boolean = true and false;', 0); + await validateOx('var myResult: boolean = true or false;', 0); - await validateOx("var myResult: boolean = 2 == 3;", 0); - await validateOx("var myResult: boolean = 2 != 3;", 0); - await validateOx("var myResult: boolean = true == false;", 0); - await validateOx("var myResult: boolean = true != false;", 0); + await validateOx('var myResult: boolean = 2 == 3;', 0); + await validateOx('var myResult: boolean = 2 != 3;', 0); + await validateOx('var myResult: boolean = true == false;', 0); + await validateOx('var myResult: boolean = true != false;', 0); - await validateOx("var myResult: boolean = true == 3;", 1); - await validateOx("var myResult: boolean = 2 != false;", 1); + await validateOx('var myResult: boolean = true == 3;', 1); + await validateOx('var myResult: boolean = 2 != false;', 1); }); - test("unary operator: !", async () => { - await validateOx("var myResult: boolean = !true;", 0); - await validateOx("var myResult: boolean = !!true;", 0); - await validateOx("var myResult: boolean = !!!true;", 0); + test('unary operator: !', async () => { + await validateOx('var myResult: boolean = !true;', 0); + await validateOx('var myResult: boolean = !!true;', 0); + await validateOx('var myResult: boolean = !!!true;', 0); }); - test("unary operator: -", async () => { - await validateOx("var myResult: number = -2;", 0); - await validateOx("var myResult: number = --2;", 0); - await validateOx("var myResult: number = ---2;", 0); + test('unary operator: -', async () => { + await validateOx('var myResult: number = -2;', 0); + await validateOx('var myResult: number = --2;', 0); + await validateOx('var myResult: number = ---2;', 0); }); - test("use overloaded operators", async () => { - await validateOx("var myVar : boolean = true == false;", 0); - await validateOx("var myVar : boolean = 2 == 3;", 0); - await validateOx("var myVar : boolean = true == 3;", 1); - await validateOx("var myVar : boolean = 2 == false;", 1); + test('use overloaded operators', async () => { + await validateOx('var myVar : boolean = true == false;', 0); + await validateOx('var myVar : boolean = 2 == 3;', 0); + await validateOx('var myVar : boolean = true == 3;', 1); + await validateOx('var myVar : boolean = 2 == false;', 1); }); test('Only a single problem with the inner expression, since the type of "*" is always number!', async () => { - await validateOx("var myVar : number = 2 * (2 * false);", [ + await validateOx('var myVar : number = 2 * (2 * false);', [ "While validating the AstNode '(2 * false)', this error is found: The given operands for the call of '*' don't match.", ]); }); test('Two issues in nested expressions, since "*" expects always numbers, while "and" returns always booleans!', async () => { - await validateOx("var myVar : number = 2 * (2 and false);", [ + await validateOx('var myVar : number = 2 * (2 and false);', [ // this is obvious: left and right need to have the same type "While validating the AstNode '(2 and false)', this error is found: The given operands for the call of 'and' don't match.", // '*' supports only numbers for left and right, but the right operand is always boolean as result of the 'and' operator diff --git a/examples/ox/test/ox-type-checking-statements.test.ts b/examples/ox/test/ox-type-checking-statements.test.ts index 6e202ffa..91b9c66b 100644 --- a/examples/ox/test/ox-type-checking-statements.test.ts +++ b/examples/ox/test/ox-type-checking-statements.test.ts @@ -4,44 +4,44 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { validateOx } from "./ox-type-checking-utils.js"; +import { describe, test } from 'vitest'; +import { validateOx } from './ox-type-checking-utils.js'; -describe("Test type checking for statements and variables in OX", () => { - test("multiple nested and", async () => { - await validateOx("var myResult: boolean = true and false;", 0); - await validateOx("var myResult: boolean = true and false and true;", 0); +describe('Test type checking for statements and variables in OX', () => { + test('multiple nested and', async () => { + await validateOx('var myResult: boolean = true and false;', 0); + await validateOx('var myResult: boolean = true and false and true;', 0); }); - test("number assignments", async () => { - await validateOx("var myResult: number = 2;", 0); - await validateOx("var myResult: number = 2 * 3;", 0); - await validateOx("var myResult: number = 2 < 3;", 1); - await validateOx("var myResult: number = true;", 1); + test('number assignments', async () => { + await validateOx('var myResult: number = 2;', 0); + await validateOx('var myResult: number = 2 * 3;', 0); + await validateOx('var myResult: number = 2 < 3;', 1); + await validateOx('var myResult: number = true;', 1); }); - test("boolean assignments", async () => { - await validateOx("var myResult: boolean = true;", 0); - await validateOx("var myResult: boolean = 2;", 1); - await validateOx("var myResult: boolean = 2 * 3;", 1); - await validateOx("var myResult: boolean = 2 < 3;", 0); + test('boolean assignments', async () => { + await validateOx('var myResult: boolean = true;', 0); + await validateOx('var myResult: boolean = 2;', 1); + await validateOx('var myResult: boolean = 2 * 3;', 1); + await validateOx('var myResult: boolean = 2 < 3;', 0); }); - test("statement assignments", async () => { - await validateOx("var myResult: boolean; myResult = true;", 0); - await validateOx("var myResult: boolean; myResult = 2;", 1); - await validateOx("var myResult: boolean; myResult = 2 * 3;", 1); - await validateOx("var myResult: boolean; myResult = 2 < 3;", 0); + test('statement assignments', async () => { + await validateOx('var myResult: boolean; myResult = true;', 0); + await validateOx('var myResult: boolean; myResult = 2;', 1); + await validateOx('var myResult: boolean; myResult = 2 * 3;', 1); + await validateOx('var myResult: boolean; myResult = 2 < 3;', 0); }); - test("boolean in conditions", async () => { - await validateOx("if ( true ) {}", 0); - await validateOx("if ( 3 ) {}", 1); + test('boolean in conditions', async () => { + await validateOx('if ( true ) {}', 0); + await validateOx('if ( 3 ) {}', 1); }); - test("variable declarations", async () => { - await validateOx("var myVar : boolean;", 0); - await validateOx("var myVar : number;", 0); - await validateOx("var myVar : void;", 1); + test('variable declarations', async () => { + await validateOx('var myVar : boolean;', 0); + await validateOx('var myVar : number;', 0); + await validateOx('var myVar : void;', 1); }); }); diff --git a/examples/ox/test/ox-type-checking-utils.ts b/examples/ox/test/ox-type-checking-utils.ts index b251bff3..102aef52 100644 --- a/examples/ox/test/ox-type-checking-utils.ts +++ b/examples/ox/test/ox-type-checking-utils.ts @@ -4,32 +4,32 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EmptyFileSystem } from "langium"; -import { parseDocument } from "langium/test"; -import { isFunctionType } from "typir"; -import { deleteAllDocuments } from "typir-langium"; -import { compareValidationIssuesStrict, expectTypirTypes } from "typir/test"; -import { afterEach, expect } from "vitest"; -import { createOxServices } from "../src/language/ox-module.js"; +import { EmptyFileSystem } from 'langium'; +import { parseDocument } from 'langium/test'; +import { isFunctionType } from 'typir'; +import { deleteAllDocuments } from 'typir-langium'; +import { compareValidationIssuesStrict, expectTypirTypes } from 'typir/test'; +import { afterEach, expect } from 'vitest'; +import { createOxServices } from '../src/language/ox-module.js'; export const oxServices = createOxServices(EmptyFileSystem).Ox; export const operatorNames = [ - "-", - "*", - "/", - "+", - "<", - "<=", - ">", - ">=", - "and", - "or", - "==", - "==", - "!=", - "!=", - "!", - "-", + '-', + '*', + '/', + '+', + '<', + '<=', + '>', + '>=', + 'and', + 'or', + '==', + '==', + '!=', + '!=', + '!', + '-', ]; afterEach(async () => { @@ -46,10 +46,10 @@ export async function validateOx( const diagnostics: string[] = ( await oxServices.validation.DocumentValidator.validateDocument(document) ).map((d) => d.message); - const msgError = diagnostics.join("\n"); - if (typeof errors === "number") { + const msgError = diagnostics.join('\n'); + if (typeof errors === 'number') { expect(diagnostics, msgError).toHaveLength(errors); - } else if (typeof errors === "string") { + } else if (typeof errors === 'string') { expect(diagnostics, msgError).toHaveLength(1); expect(diagnostics[0], msgError).includes(errors); } else { diff --git a/package-lock.json b/package-lock.json index 8ed62d50..75feab9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "examples/lox" ], "devDependencies": { + "@stylistic/eslint-plugin": "^4.4.0", "@types/node": "~18.19.55", "@typescript-eslint/eslint-plugin": "^8.33.0", "@vitest/ui": "~2.1.2", @@ -23,8 +24,9 @@ "esbuild": "^0.25.0", "eslint": "^9.27.0", "eslint-plugin-header": "~3.1.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-unused-imports": "^4.1.4", "fs-extra": "^11.2.0", - "prettier": "^3.5.3", "semver": "^7.7.1", "shx": "~0.3.4", "typescript": "~5.8.2", @@ -1002,6 +1004,59 @@ "win32" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.0.tgz", + "integrity": "sha512-bIh/d9X+OQLCAMdhHtps+frvyjvAM4B1YlSJzcEEhl7wXLIqPar3ngn9DrHhkBOrTA/z9J0bUMtctAspe0dxdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.32.1", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1015,6 +1070,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.19.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", @@ -1497,6 +1559,126 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1506,6 +1688,32 @@ "node": ">=12" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1543,6 +1751,56 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1743,6 +2001,60 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -1775,6 +2087,70 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/editorconfig": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-2.0.0.tgz", @@ -1823,24 +2199,170 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "node_modules/es-abstract": { + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", "@esbuild/darwin-x64": "0.25.0", "@esbuild/freebsd-arm64": "0.25.0", "@esbuild/freebsd-x64": "0.25.0", @@ -1945,6 +2467,56 @@ } } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/eslint-plugin-header": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", @@ -1954,6 +2526,76 @@ "eslint": ">=7.7.0" } }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", + "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", @@ -2224,6 +2866,22 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -2267,6 +2925,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2276,6 +2965,63 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2323,6 +3069,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2336,140 +3112,566 @@ "dev": true, "license": "MIT" }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">= 0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2509,6 +3711,19 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2660,6 +3875,16 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2744,6 +3969,103 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2770,6 +4092,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2880,6 +4220,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", @@ -2917,22 +4267,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2982,6 +4316,50 @@ "node": ">= 0.10" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3097,6 +4475,61 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -3108,6 +4541,55 @@ "node": ">=10" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3171,6 +4653,82 @@ "node": ">=6" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3226,6 +4784,65 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3238,6 +4855,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3397,6 +5024,19 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -3415,6 +5055,84 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -3444,6 +5162,25 @@ "resolved": "packages/typir-langium", "link": true }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -4109,6 +5846,95 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index 29cdb573..8f474d1f 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "build": "tsc -b tsconfig.build.json && npm run build --workspaces", "watch": "concurrently -n typir,typir-langium,ox,lox -c blue,blue,green,green \"tsc -b tsconfig.build.json -w\" \"npm run watch --workspace=typir\" \"npm run watch --workspace=typir-langium\" \"npm run watch --workspace=examples/ox\" \"npm run watch --workspace=examples/lox\"", "lint": "eslint .", - "pretty": "prettier -w \"**/*.ts\"", - "pretty:check": "prettier -c \"**/*.ts\"", + "lint:fix": "eslint . --fix", "test": "vitest", "test:run": "vitest --run", "test-ui": "vitest --ui", @@ -32,6 +31,7 @@ "version:dependencies": "node ./scripts/update-version.js && npm install" }, "devDependencies": { + "@stylistic/eslint-plugin": "^4.4.0", "@types/node": "~18.19.55", "@typescript-eslint/eslint-plugin": "^8.33.0", "@vitest/ui": "~2.1.2", @@ -40,8 +40,9 @@ "esbuild": "^0.25.0", "eslint": "^9.27.0", "eslint-plugin-header": "~3.1.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-unused-imports": "^4.1.4", "fs-extra": "^11.2.0", - "prettier": "^3.5.3", "semver": "^7.7.1", "shx": "~0.3.4", "typescript": "~5.8.2", diff --git a/packages/typir-langium/src/features/langium-caching.ts b/packages/typir-langium/src/features/langium-caching.ts index dfde5cd2..c5940a80 100644 --- a/packages/typir-langium/src/features/langium-caching.ts +++ b/packages/typir-langium/src/features/langium-caching.ts @@ -4,18 +4,15 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { - AstNode, - DocumentCache, - DocumentState, - LangiumSharedCoreServices, -} from "langium"; -import { CachePending, LanguageNodeInferenceCaching, Type } from "typir"; -import { getDocumentKey } from "../utils/typir-langium-utils.js"; +import type { AstNode, LangiumSharedCoreServices } from 'langium'; +import { DocumentCache, DocumentState } from 'langium'; +import type { LanguageNodeInferenceCaching, Type } from 'typir'; +import { CachePending } from 'typir'; +import { getDocumentKey } from '../utils/typir-langium-utils.js'; // cache AstNodes export class LangiumLanguageNodeInferenceCaching - implements LanguageNodeInferenceCaching +implements LanguageNodeInferenceCaching { protected readonly cache: DocumentCache; // removes cached AstNodes, if their underlying LangiumDocuments are invalidated diff --git a/packages/typir-langium/src/features/langium-inference.ts b/packages/typir-langium/src/features/langium-inference.ts index 227ed60b..408a6e75 100644 --- a/packages/typir-langium/src/features/langium-inference.ts +++ b/packages/typir-langium/src/features/langium-inference.ts @@ -4,13 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode } from "langium"; -import { - DefaultTypeInferenceCollector, - TypeInferenceCollector, - TypeInferenceRule, -} from "typir"; -import { LangiumAstTypes } from "../utils/typir-langium-utils.js"; +import type { AstNode } from 'langium'; +import type { TypeInferenceCollector, TypeInferenceRule } from 'typir'; +import { DefaultTypeInferenceCollector } from 'typir'; +import type { LangiumAstTypes } from '../utils/typir-langium-utils.js'; export type LangiumTypeInferenceRules = { [K in keyof T]?: T[K] extends AstNode @@ -32,8 +29,8 @@ export interface LangiumTypeInferenceCollector } export class DefaultLangiumTypeInferenceCollector< - AstTypes extends LangiumAstTypes, - > + AstTypes extends LangiumAstTypes, +> extends DefaultTypeInferenceCollector implements LangiumTypeInferenceCollector { @@ -42,7 +39,7 @@ export class DefaultLangiumTypeInferenceCollector< ): void { // map this approach for registering inference rules to the key-value approach from core Typir for (const [type, ruleCallbacks] of Object.entries(rules)) { - const languageKey = type === "AstNode" ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key + const languageKey = type === 'AstNode' ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key const callbacks = ruleCallbacks as | TypeInferenceRule | Array>; diff --git a/packages/typir-langium/src/features/langium-language.ts b/packages/typir-langium/src/features/langium-language.ts index 46ac551c..94f7fe38 100644 --- a/packages/typir-langium/src/features/langium-language.ts +++ b/packages/typir-langium/src/features/langium-language.ts @@ -4,12 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AbstractAstReflection, AstNode } from "langium"; -import { removeFromArray } from "typir"; -import { - DefaultLanguageService, - LanguageService, -} from "../../../typir/lib/services/language.js"; +import type { AbstractAstReflection, AstNode } from 'langium'; +import { removeFromArray } from 'typir'; +import type { LanguageService } from '../../../typir/lib/services/language.js'; +import { DefaultLanguageService } from '../../../typir/lib/services/language.js'; /** * The default implementation of the 'LanguageService' for Langium exploits the generated XXXAstReflection, diff --git a/packages/typir-langium/src/features/langium-printing.ts b/packages/typir-langium/src/features/langium-printing.ts index 35a62fe6..69cfe2c8 100644 --- a/packages/typir-langium/src/features/langium-printing.ts +++ b/packages/typir-langium/src/features/langium-printing.ts @@ -4,8 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, isAstNode } from "langium"; -import { DefaultTypeConflictPrinter } from "typir"; +import type { AstNode } from 'langium'; +import { isAstNode } from 'langium'; +import { DefaultTypeConflictPrinter } from 'typir'; export class LangiumProblemPrinter extends DefaultTypeConflictPrinter { /** When printing a language node, i.e. an AstNode, print the text of the corresponding CstNode. */ @@ -14,7 +15,7 @@ export class LangiumProblemPrinter extends DefaultTypeConflictPrinter { sentenceBegin?: boolean | undefined, ): string { if (isAstNode(languageNode)) { - return `${sentenceBegin ? "T" : "t"}he AstNode '${languageNode.$cstNode?.text}'`; + return `${sentenceBegin ? 'T' : 't'}he AstNode '${languageNode.$cstNode?.text}'`; } return super.printLanguageNode(languageNode, sentenceBegin); } diff --git a/packages/typir-langium/src/features/langium-type-creator.ts b/packages/typir-langium/src/features/langium-type-creator.ts index 7da398de..297d5e49 100644 --- a/packages/typir-langium/src/features/langium-type-creator.ts +++ b/packages/typir-langium/src/features/langium-type-creator.ts @@ -4,21 +4,19 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { AstNode, - AstUtils, - DocumentState, - interruptAndCheck, LangiumDocument, LangiumSharedCoreServices, -} from "langium"; -import { Type, TypeGraph, TypeGraphListener } from "typir"; -import { TypirLangiumServices } from "../typir-langium.js"; +} from 'langium'; +import { AstUtils, DocumentState, interruptAndCheck } from 'langium'; +import type { Type, TypeGraph, TypeGraphListener } from 'typir'; +import type { TypirLangiumServices } from '../typir-langium.js'; +import type { LangiumAstTypes } from '../utils/typir-langium-utils.js'; import { getDocumentKeyForDocument, getDocumentKeyForURI, - LangiumAstTypes, -} from "../utils/typir-langium-utils.js"; +} from '../utils/typir-langium-utils.js'; /** * This service provides the API to define the actual types, inference rules and validation rules @@ -58,10 +56,10 @@ export interface LangiumTypeCreator { } export class DefaultLangiumTypeCreator - implements LangiumTypeCreator, TypeGraphListener +implements LangiumTypeCreator, TypeGraphListener { protected initialized: boolean = false; - protected currentDocumentKey: string = ""; + protected currentDocumentKey: string = ''; protected readonly documentTypesMap: Map = new Map(); protected readonly typir: TypirLangiumServices; protected readonly typeGraph: TypeGraph; @@ -129,7 +127,7 @@ export class DefaultLangiumTypeCreator this.typeSystemDefinition.onNewAstNode(node, this.typir), ); - this.currentDocumentKey = ""; // reset the key, newly created types will be associated with no document now + this.currentDocumentKey = ''; // reset the key, newly created types will be associated with no document now } protected invalidateTypesOfDocument(documentKey: string): void { diff --git a/packages/typir-langium/src/features/langium-validation.ts b/packages/typir-langium/src/features/langium-validation.ts index a856f441..a46c019b 100644 --- a/packages/typir-langium/src/features/langium-validation.ts +++ b/packages/typir-langium/src/features/langium-validation.ts @@ -4,21 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { AstNode, LangiumDefaultCoreServices, ValidationAcceptor, ValidationChecks, -} from "langium"; -import { - DefaultValidationCollector, +} from 'langium'; +import type { TypirServices, ValidationCollector, ValidationProblem, ValidationRule, -} from "typir"; -import { TypirLangiumServices } from "../typir-langium.js"; -import { LangiumAstTypes } from "../utils/typir-langium-utils.js"; +} from 'typir'; +import { DefaultValidationCollector } from 'typir'; +import type { TypirLangiumServices } from '../typir-langium.js'; +import type { LangiumAstTypes } from '../utils/typir-langium-utils.js'; export function registerTypirValidationChecks( langiumServices: LangiumDefaultCoreServices, @@ -95,7 +95,7 @@ export interface LangiumTypirValidator { } export class DefaultLangiumTypirValidator - implements LangiumTypirValidator +implements LangiumTypirValidator { protected readonly services: TypirServices; @@ -188,7 +188,7 @@ export class DefaultLangiumValidationCollector ): void { // map this approach for registering validation rules to the key-value approach from core Typir for (const [type, ruleCallbacks] of Object.entries(rules)) { - const languageKey = type === "AstNode" ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key + const languageKey = type === 'AstNode' ? undefined : type; // using 'AstNode' as key is equivalent to specifying no key const callbacks = ruleCallbacks as | ValidationRule | Array>; diff --git a/packages/typir-langium/src/index.ts b/packages/typir-langium/src/index.ts index 7b218a36..54decb8f 100644 --- a/packages/typir-langium/src/index.ts +++ b/packages/typir-langium/src/index.ts @@ -4,11 +4,11 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -export * from "./typir-langium.js"; -export * from "./features/langium-caching.js"; -export * from "./features/langium-inference.js"; -export * from "./features/langium-language.js"; -export * from "./features/langium-printing.js"; -export * from "./features/langium-type-creator.js"; -export * from "./features/langium-validation.js"; -export * from "./utils/typir-langium-utils.js"; +export * from './typir-langium.js'; +export * from './features/langium-caching.js'; +export * from './features/langium-inference.js'; +export * from './features/langium-language.js'; +export * from './features/langium-printing.js'; +export * from './features/langium-type-creator.js'; +export * from './features/langium-validation.js'; +export * from './utils/typir-langium-utils.js'; diff --git a/packages/typir-langium/src/typir-langium.ts b/packages/typir-langium/src/typir-langium.ts index e6eb0ad8..6ea4352a 100644 --- a/packages/typir-langium/src/typir-langium.ts +++ b/packages/typir-langium/src/typir-langium.ts @@ -4,40 +4,39 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { AbstractAstReflection, AstNode, LangiumDefaultCoreServices, LangiumSharedCoreServices, -} from "langium"; -import { - createDefaultTypirServicesModule, +} from 'langium'; +import type { DeepPartial, - inject, Module, PartialTypirServices, TypirServices, -} from "typir"; -import { LangiumLanguageNodeInferenceCaching } from "./features/langium-caching.js"; -import { - DefaultLangiumTypeInferenceCollector, - LangiumTypeInferenceCollector, -} from "./features/langium-inference.js"; -import { LangiumLanguageService } from "./features/langium-language.js"; -import { LangiumProblemPrinter } from "./features/langium-printing.js"; -import { - DefaultLangiumTypeCreator, +} from 'typir'; +import { createDefaultTypirServicesModule, inject } from 'typir'; +import { LangiumLanguageNodeInferenceCaching } from './features/langium-caching.js'; +import type { LangiumTypeInferenceCollector } from './features/langium-inference.js'; +import { DefaultLangiumTypeInferenceCollector } from './features/langium-inference.js'; +import { LangiumLanguageService } from './features/langium-language.js'; +import { LangiumProblemPrinter } from './features/langium-printing.js'; +import type { LangiumTypeCreator, LangiumTypeSystemDefinition, -} from "./features/langium-type-creator.js"; +} from './features/langium-type-creator.js'; +import { DefaultLangiumTypeCreator } from './features/langium-type-creator.js'; +import type { + LangiumTypirValidator, + LangiumValidationCollector, +} from './features/langium-validation.js'; import { DefaultLangiumTypirValidator, DefaultLangiumValidationCollector, - LangiumTypirValidator, - LangiumValidationCollector, registerTypirValidationChecks, -} from "./features/langium-validation.js"; -import { LangiumAstTypes } from "./utils/typir-langium-utils.js"; +} from './features/langium-validation.js'; +import type { LangiumAstTypes } from './utils/typir-langium-utils.js'; /** * Additional Typir-Langium services to manage the Typir services @@ -101,7 +100,7 @@ export function createDefaultTypirLangiumServicesModule< TypeCreator: (typirServices) => new DefaultLangiumTypeCreator(typirServices, langiumServices), TypeSystemDefinition: () => { - throw new Error("The type system needs to be specified!"); + throw new Error('The type system needs to be specified!'); }, // to be replaced later }, validation: { diff --git a/packages/typir-langium/src/utils/typir-langium-utils.ts b/packages/typir-langium/src/utils/typir-langium-utils.ts index 90997af3..da783241 100644 --- a/packages/typir-langium/src/utils/typir-langium-utils.ts +++ b/packages/typir-langium/src/utils/typir-langium-utils.ts @@ -4,14 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { AstNode, - AstUtils, LangiumDocument, LangiumSharedCoreServices, URI, -} from "langium"; -import { assertTrue } from "typir"; +} from 'langium'; +import { AstUtils } from 'langium'; +import { assertTrue } from 'typir'; export function getDocumentKeyForURI(document: URI): string { return document.toString(); diff --git a/packages/typir/src/graph/graph-algorithms.ts b/packages/typir/src/graph/graph-algorithms.ts index 71f97268..f829944b 100644 --- a/packages/typir/src/graph/graph-algorithms.ts +++ b/packages/typir/src/graph/graph-algorithms.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypirServices } from "../typir.js"; -import { TypeEdge } from "./type-edge.js"; -import { TypeGraph } from "./type-graph.js"; -import { Type } from "./type-node.js"; +import type { TypirServices } from '../typir.js'; +import type { TypeEdge } from './type-edge.js'; +import type { TypeGraph } from './type-graph.js'; +import type { Type } from './type-node.js'; /** * Graph algorithms to do calculations on the type graph. @@ -16,19 +16,19 @@ import { Type } from "./type-node.js"; export interface GraphAlgorithms { collectReachableTypes( from: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): Set; existsEdgePath( from: Type, to: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): boolean; getEdgePath( from: Type, to: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): TypeEdge[]; } @@ -42,7 +42,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { collectReachableTypes( from: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): Set { const result: Set = new Set(); @@ -55,7 +55,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { ); for (const edge of outgoingEdges) { if ( - edge.cachingInformation === "LINK_EXISTS" && + edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge)) ) { if (result.has(edge.to)) { @@ -74,7 +74,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { existsEdgePath( from: Type, to: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): boolean { const visited: Set = new Set(); @@ -89,7 +89,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { ); for (const edge of outgoingEdges) { if ( - edge.cachingInformation === "LINK_EXISTS" && + edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge)) ) { if (edge.to === to) { @@ -117,7 +117,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { getEdgePath( from: Type, to: Type, - $relations: Array, + $relations: Array, filterEdges?: (edgr: TypeEdge) => boolean, ): TypeEdge[] { const visited: Map = new Map(); // the edge from the parent to the current node @@ -132,7 +132,7 @@ export class DefaultGraphAlgorithms implements GraphAlgorithms { ); for (const edge of outgoingEdges) { if ( - edge.cachingInformation === "LINK_EXISTS" && + edge.cachingInformation === 'LINK_EXISTS' && (filterEdges === undefined || filterEdges(edge)) ) { if (edge.to === to) { diff --git a/packages/typir/src/graph/type-edge.ts b/packages/typir/src/graph/type-edge.ts index 22f656dd..2d709c34 100644 --- a/packages/typir/src/graph/type-edge.ts +++ b/packages/typir/src/graph/type-edge.ts @@ -4,8 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EdgeCachingInformation } from "../services/caching.js"; -import { Type } from "./type-node.js"; +import type { EdgeCachingInformation } from '../services/caching.js'; +import type { Type } from './type-node.js'; /** * An edge has a direction (from --> to) and can be querried from both types (incomingEdge, outgoingEdge). @@ -30,8 +30,8 @@ export interface TypeEdge { export function isTypeEdge(edge: unknown): edge is TypeEdge { return ( - typeof edge === "object" && + typeof edge === 'object' && edge !== null && - typeof (edge as TypeEdge).$relation === "string" + typeof (edge as TypeEdge).$relation === 'string' ); } diff --git a/packages/typir/src/graph/type-graph.ts b/packages/typir/src/graph/type-graph.ts index 4b7960d0..1019d169 100644 --- a/packages/typir/src/graph/type-graph.ts +++ b/packages/typir/src/graph/type-graph.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EdgeCachingInformation } from "../services/caching.js"; -import { assertTrue, removeFromArray } from "../utils/utils.js"; -import { TypeEdge } from "./type-edge.js"; -import { Type } from "./type-node.js"; +import type { EdgeCachingInformation } from '../services/caching.js'; +import { assertTrue, removeFromArray } from '../utils/utils.js'; +import type { TypeEdge } from './type-edge.js'; +import type { Type } from './type-node.js'; /** * Each Typir instance has one single type graph. @@ -31,7 +31,7 @@ export class TypeGraph { */ addNode(type: Type, key?: string): void { if (!key) { - assertTrue(type.isInStateOrLater("Identifiable")); // the key of the type must be available! + assertTrue(type.isInStateOrLater('Identifiable')); // the key of the type must be available! } const mapKey = key ?? type.getIdentifier(); if (this.nodes.has(mapKey)) { @@ -131,8 +131,8 @@ export class TypeGraph { getUnidirectionalEdge( from: Type, to: Type, - $relation: T["$relation"], - cachingMode: EdgeCachingInformation = "LINK_EXISTS", + $relation: T['$relation'], + cachingMode: EdgeCachingInformation = 'LINK_EXISTS', ): T | undefined { return from .getOutgoingEdges($relation) @@ -145,8 +145,8 @@ export class TypeGraph { getBidirectionalEdge( from: Type, to: Type, - $relation: T["$relation"], - cachingMode: EdgeCachingInformation = "LINK_EXISTS", + $relation: T['$relation'], + cachingMode: EdgeCachingInformation = 'LINK_EXISTS', ): T | undefined { // for bidirectional edges, check outgoing and incoming edges, since the graph contains only a single edge! return from diff --git a/packages/typir/src/graph/type-node.ts b/packages/typir/src/graph/type-node.ts index 430ec97b..72fac268 100644 --- a/packages/typir/src/graph/type-node.ts +++ b/packages/typir/src/graph/type-node.ts @@ -4,19 +4,20 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeReference } from "../initialization/type-reference.js"; +import type { TypeReference } from '../initialization/type-reference.js'; import { WaitingForIdentifiableAndCompletedTypeReferences, WaitingForInvalidTypeReferences, -} from "../initialization/type-waiting.js"; -import { Kind, isKind } from "../kinds/kind.js"; -import { TypirProblem } from "../utils/utils-definitions.js"; +} from '../initialization/type-waiting.js'; +import type { Kind } from '../kinds/kind.js'; +import { isKind } from '../kinds/kind.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; import { assertTrue, assertUnreachable, removeFromArray, -} from "../utils/utils.js"; -import { TypeEdge } from "./type-edge.js"; +} from '../utils/utils.js'; +import type { TypeEdge } from './type-edge.js'; /** * The transitions between the states of a type are depicted as state machine: @@ -33,7 +34,7 @@ stateDiagram-v2 * A state is 'Invalid' otherwise. * 'Invalid' is made explicit, since it might require less dependencies than 'Completed' and therefore speed-ups the resolution of dependencies. */ -export type TypeInitializationState = "Invalid" | "Identifiable" | "Completed"; +export type TypeInitializationState = 'Invalid' | 'Identifiable' | 'Completed'; export interface PreconditionsForInitializationState { referencesToBeIdentifiable?: Array>; // or later/more @@ -117,7 +118,7 @@ export abstract class Type { // store the state of the initialization process of this type - protected initializationState: TypeInitializationState = "Invalid"; + protected initializationState: TypeInitializationState = 'Invalid'; getInitializationState(): TypeInitializationState { return this.initializationState; @@ -153,12 +154,12 @@ export abstract class Type { } isInStateOrLater(state: TypeInitializationState): boolean { switch (state) { - case "Invalid": + case 'Invalid': return true; - case "Identifiable": - return this.initializationState !== "Invalid"; - case "Completed": - return this.initializationState === "Completed"; + case 'Identifiable': + return this.initializationState !== 'Invalid'; + case 'Completed': + return this.initializationState === 'Completed'; default: assertUnreachable(state); } @@ -176,13 +177,13 @@ export abstract class Type { if (informIfNotInvalidAnymore) { const currentState = this.getInitializationState(); switch (currentState) { - case "Invalid": + case 'Invalid': // don't inform about the Invalid state! break; - case "Identifiable": + case 'Identifiable': newListeners.onSwitchedToIdentifiable(this); break; - case "Completed": + case 'Completed': newListeners.onSwitchedToIdentifiable(this); // inform about both Identifiable and Completed! newListeners.onSwitchedToCompleted(this); break; @@ -323,27 +324,27 @@ export abstract class Type { } protected switchFromInvalidToIdentifiable(): void { - this.assertState("Invalid"); + this.assertState('Invalid'); this.onIdentification(); - this.initializationState = "Identifiable"; + this.initializationState = 'Identifiable'; this.stateListeners .slice() .forEach((listener) => listener.onSwitchedToIdentifiable(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromIdentifiableToCompleted(): void { - this.assertState("Identifiable"); + this.assertState('Identifiable'); this.onCompletion(); - this.initializationState = "Completed"; + this.initializationState = 'Completed'; this.stateListeners .slice() .forEach((listener) => listener.onSwitchedToCompleted(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromCompleteOrIdentifiableToInvalid(): void { - if (this.isNotInState("Invalid")) { + if (this.isNotInState('Invalid')) { this.onInvalidation(); - this.initializationState = "Invalid"; + this.initializationState = 'Invalid'; this.stateListeners .slice() .forEach((listener) => listener.onSwitchedToInvalid(this)); // slice() prevents issues with removal of listeners during notifications @@ -411,13 +412,13 @@ export abstract class Type { return false; } - getIncomingEdges($relation: T["$relation"]): T[] { + getIncomingEdges($relation: T['$relation']): T[] { return (this.edgesIncoming.get($relation) as T[]) ?? []; } - getOutgoingEdges($relation: T["$relation"]): T[] { + getOutgoingEdges($relation: T['$relation']): T[] { return (this.edgesOutgoing.get($relation) as T[]) ?? []; } - getEdges($relation: T["$relation"]): T[] { + getEdges($relation: T['$relation']): T[] { return [ ...this.getIncomingEdges($relation), ...this.getOutgoingEdges($relation), @@ -437,9 +438,9 @@ export abstract class Type { export function isType(type: unknown): type is Type { return ( - typeof type === "object" && + typeof type === 'object' && type !== null && - typeof (type as Type).getIdentifier === "function" && + typeof (type as Type).getIdentifier === 'function' && isKind((type as Type).kind) ); } diff --git a/packages/typir/src/index-test.ts b/packages/typir/src/index-test.ts index 794d7138..18b0d795 100644 --- a/packages/typir/src/index-test.ts +++ b/packages/typir/src/index-test.ts @@ -7,4 +7,4 @@ // export all utilities which are using 'vitest' and which are not located in test/ */ but in src/ here // to be imported via 'typir/test' in order not to mix up production code with 'vitest' dependencies -export * from "./utils/test-utils.js"; +export * from './utils/test-utils.js'; diff --git a/packages/typir/src/index.ts b/packages/typir/src/index.ts index ef6309de..3c4023c5 100644 --- a/packages/typir/src/index.ts +++ b/packages/typir/src/index.ts @@ -4,50 +4,50 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -export * from "./typir.js"; -export * from "./graph/type-edge.js"; -export * from "./graph/type-graph.js"; -export * from "./graph/type-node.js"; -export * from "./initialization/type-initializer.js"; -export * from "./initialization/type-reference.js"; -export * from "./initialization/type-selector.js"; -export * from "./initialization/type-waiting.js"; -export * from "./kinds/bottom/bottom-kind.js"; -export * from "./kinds/bottom/bottom-type.js"; -export * from "./kinds/class/class-initializer.js"; -export * from "./kinds/class/class-kind.js"; -export * from "./kinds/class/class-type.js"; -export * from "./kinds/class/class-validation.js"; -export * from "./kinds/class/top-class-kind.js"; -export * from "./kinds/class/top-class-type.js"; -export * from "./kinds/fixed-parameters/fixed-parameters-kind.js"; -export * from "./kinds/fixed-parameters/fixed-parameters-type.js"; -export * from "./kinds/function/function-initializer.js"; -export * from "./kinds/function/function-kind.js"; -export * from "./kinds/function/function-overloading.js"; -export * from "./kinds/function/function-type.js"; -export * from "./kinds/function/function-validation-calls.js"; -export * from "./kinds/function/function-validation-unique.js"; -export * from "./kinds/multiplicity/multiplicity-kind.js"; -export * from "./kinds/multiplicity/multiplicity-type.js"; -export * from "./kinds/primitive/primitive-kind.js"; -export * from "./kinds/primitive/primitive-type.js"; -export * from "./kinds/top/top-kind.js"; -export * from "./kinds/top/top-type.js"; -export * from "./kinds/kind.js"; -export * from "./services/assignability.js"; -export * from "./services/caching.js"; -export * from "./services/conversion.js"; -export * from "./services/equality.js"; -export * from "./services/inference.js"; -export * from "./services/kind-registry.js"; -export * from "./services/language.js"; -export * from "./services/operator.js"; -export * from "./services/printing.js"; -export * from "./services/subtype.js"; -export * from "./services/validation.js"; -export * from "./utils/dependency-injection.js"; -export * from "./utils/rule-registration.js"; -export * from "./utils/utils.js"; -export * from "./utils/utils-definitions.js"; -export * from "./utils/utils-type-comparison.js"; +export * from './typir.js'; +export * from './graph/type-edge.js'; +export * from './graph/type-graph.js'; +export * from './graph/type-node.js'; +export * from './initialization/type-initializer.js'; +export * from './initialization/type-reference.js'; +export * from './initialization/type-selector.js'; +export * from './initialization/type-waiting.js'; +export * from './kinds/bottom/bottom-kind.js'; +export * from './kinds/bottom/bottom-type.js'; +export * from './kinds/class/class-initializer.js'; +export * from './kinds/class/class-kind.js'; +export * from './kinds/class/class-type.js'; +export * from './kinds/class/class-validation.js'; +export * from './kinds/class/top-class-kind.js'; +export * from './kinds/class/top-class-type.js'; +export * from './kinds/fixed-parameters/fixed-parameters-kind.js'; +export * from './kinds/fixed-parameters/fixed-parameters-type.js'; +export * from './kinds/function/function-initializer.js'; +export * from './kinds/function/function-kind.js'; +export * from './kinds/function/function-overloading.js'; +export * from './kinds/function/function-type.js'; +export * from './kinds/function/function-validation-calls.js'; +export * from './kinds/function/function-validation-unique.js'; +export * from './kinds/multiplicity/multiplicity-kind.js'; +export * from './kinds/multiplicity/multiplicity-type.js'; +export * from './kinds/primitive/primitive-kind.js'; +export * from './kinds/primitive/primitive-type.js'; +export * from './kinds/top/top-kind.js'; +export * from './kinds/top/top-type.js'; +export * from './kinds/kind.js'; +export * from './services/assignability.js'; +export * from './services/caching.js'; +export * from './services/conversion.js'; +export * from './services/equality.js'; +export * from './services/inference.js'; +export * from './services/kind-registry.js'; +export * from './services/language.js'; +export * from './services/operator.js'; +export * from './services/printing.js'; +export * from './services/subtype.js'; +export * from './services/validation.js'; +export * from './utils/dependency-injection.js'; +export * from './utils/rule-registration.js'; +export * from './utils/utils.js'; +export * from './utils/utils-definitions.js'; +export * from './utils/utils-type-comparison.js'; diff --git a/packages/typir/src/initialization/type-initializer.ts b/packages/typir/src/initialization/type-initializer.ts index f43e8144..c816435a 100644 --- a/packages/typir/src/initialization/type-initializer.ts +++ b/packages/typir/src/initialization/type-initializer.ts @@ -4,8 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; export type TypeInitializerListener = (type: T) => void; @@ -38,7 +38,7 @@ export abstract class TypeInitializer { protected producedType(newType: T): T { const key = newType.getIdentifier(); if (!key) { - throw new Error("missing identifier!"); + throw new Error('missing identifier!'); } const existingType = this.services.infrastructure.Graph.getType(key); if (existingType) { diff --git a/packages/typir/src/initialization/type-reference.ts b/packages/typir/src/initialization/type-reference.ts index 8db2fa27..cb01f934 100644 --- a/packages/typir/src/initialization/type-reference.ts +++ b/packages/typir/src/initialization/type-reference.ts @@ -4,16 +4,16 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../graph/type-graph.js"; -import { Type } from "../graph/type-node.js"; -import { +import type { TypeGraphListener } from '../graph/type-graph.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypeInferenceCollectorListener, TypeInferenceRule, TypeInferenceRuleOptions, -} from "../services/inference.js"; -import { TypirServices } from "../typir.js"; -import { removeFromArray } from "../utils/utils.js"; -import { TypeSelector } from "./type-selector.js"; +} from '../services/inference.js'; +import type { TypirServices } from '../typir.js'; +import { removeFromArray } from '../utils/utils.js'; +import type { TypeSelector } from './type-selector.js'; /** * A listener for TypeReferences, who will be informed about the resolved/found type of the current TypeReference. @@ -54,7 +54,7 @@ export interface TypeReferenceListener { * Once the type is resolved, listeners are notified about this and all following changes of its state. */ export class TypeReference - implements TypeGraphListener, TypeInferenceCollectorListener +implements TypeGraphListener, TypeInferenceCollectorListener { protected readonly selector: TypeSelector; protected readonly services: TypirServices; @@ -110,12 +110,12 @@ export class TypeReference * @returns the result of the currently executed resolution */ protected resolve(): - | "ALREADY_RESOLVED" - | "SUCCESSFULLY_RESOLVED" - | "RESOLVING_FAILED" { + | 'ALREADY_RESOLVED' + | 'SUCCESSFULLY_RESOLVED' + | 'RESOLVING_FAILED' { if (this.resolvedType) { // the type is already resolved => nothing to do - return "ALREADY_RESOLVED"; + return 'ALREADY_RESOLVED'; } // try to resolve the type @@ -134,10 +134,10 @@ export class TypeReference .forEach((listener) => listener.onTypeReferenceResolved(this, resolvedType), ); - return "SUCCESSFULLY_RESOLVED"; + return 'SUCCESSFULLY_RESOLVED'; } else { // the type is not resolved (yet) - return "RESOLVING_FAILED"; + return 'RESOLVING_FAILED'; } } diff --git a/packages/typir/src/initialization/type-selector.ts b/packages/typir/src/initialization/type-selector.ts index 917ef858..7ada2a4b 100644 --- a/packages/typir/src/initialization/type-selector.ts +++ b/packages/typir/src/initialization/type-selector.ts @@ -4,10 +4,11 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { TypeInitializer } from "./type-initializer.js"; -import { TypeReference } from "./type-reference.js"; +import type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import { TypeInitializer } from './type-initializer.js'; +import { TypeReference } from './type-reference.js'; // TODO find better names: TypeSpecification, TypeDesignation/Designator, ... ? export type BasicTypeSelector = @@ -47,7 +48,7 @@ export interface TypeResolvingService { } export class DefaultTypeResolver - implements TypeResolvingService +implements TypeResolvingService { protected readonly services: TypirServices; @@ -61,13 +62,13 @@ export class DefaultTypeResolver if (isType(selector)) { // TODO is there a way to explicitly enforce/ensure "as T"? return selector as T; - } else if (typeof selector === "string") { + } else if (typeof selector === 'string') { return this.services.infrastructure.Graph.getType(selector) as T; } else if (selector instanceof TypeInitializer) { return selector.getTypeInitial(); } else if (selector instanceof TypeReference) { return selector.getType(); - } else if (typeof selector === "function") { + } else if (typeof selector === 'function') { // execute the function and try to recursively resolve the returned result again return this.tryToResolve( (selector as () => BasicTypeSelector).call( @@ -89,7 +90,7 @@ export class DefaultTypeResolver resolve(selector: TypeSelector): T { if (isType(selector)) { return selector as T; - } else if (typeof selector === "string") { + } else if (typeof selector === 'string') { return this.handleError( this.services.infrastructure.Graph.getType(selector) as | T @@ -104,9 +105,9 @@ export class DefaultTypeResolver } else if (selector instanceof TypeReference) { return this.handleError( selector.getType(), - "This TypeReference has no resolved type.", + 'This TypeReference has no resolved type.', ); - } else if (typeof selector === "function") { + } else if (typeof selector === 'function') { // execute the function and try to recursively resolve the returned result again return this.resolve( (selector as () => BasicTypeSelector).call( diff --git a/packages/typir/src/initialization/type-waiting.ts b/packages/typir/src/initialization/type-waiting.ts index 8ed61f49..3abb34a2 100644 --- a/packages/typir/src/initialization/type-waiting.ts +++ b/packages/typir/src/initialization/type-waiting.ts @@ -4,9 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeStateListener } from "../graph/type-node.js"; -import { removeFromArray, toArray } from "../utils/utils.js"; -import { TypeReferenceListener, TypeReference } from "./type-reference.js"; +import type { Type, TypeStateListener } from '../graph/type-node.js'; +import { removeFromArray, toArray } from '../utils/utils.js'; +import type { TypeReferenceListener, TypeReference } from './type-reference.js'; export interface WaitingForIdentifiableAndCompletedTypeReferencesListener< T extends Type, @@ -25,7 +25,7 @@ export interface WaitingForIdentifiableAndCompletedTypeReferencesListener< * if at least one of the TypeReferences was unresolved/invalid, but later on all TypeReferences are again in the desired state, and so on. */ export class WaitingForIdentifiableAndCompletedTypeReferences - implements TypeReferenceListener, TypeStateListener +implements TypeReferenceListener, TypeStateListener { /** Remembers whether all TypeReferences are in the desired states (true) or not (false). */ protected fulfilled: boolean = false; @@ -123,7 +123,7 @@ export class WaitingForIdentifiableAndCompletedTypeReferences // ... which should be identifiable (or completed) for (const ref of this.waitForRefsIdentified ?? []) { const refType = ref.getType(); - if (refType?.isInStateOrLater("Identifiable")) { + if (refType?.isInStateOrLater('Identifiable')) { // this reference is already ready } else { refType?.ignoreDependingTypesDuringInitialization( @@ -134,7 +134,7 @@ export class WaitingForIdentifiableAndCompletedTypeReferences // ... which should be completed for (const ref of this.waitForRefsCompleted ?? []) { const refType = ref.getType(); - if (refType?.isInStateOrLater("Completed")) { + if (refType?.isInStateOrLater('Completed')) { // this reference is already ready } else { refType?.ignoreDependingTypesDuringInitialization( @@ -198,7 +198,7 @@ export class WaitingForIdentifiableAndCompletedTypeReferences const refType = ref.getType(); if ( refType && - (refType.isInStateOrLater("Identifiable") || + (refType.isInStateOrLater('Identifiable') || this.typesToIgnoreForCycles.has(refType)) ) { // that is fine @@ -210,7 +210,7 @@ export class WaitingForIdentifiableAndCompletedTypeReferences const refType = ref.getType(); if ( refType && - (refType.isInStateOrLater("Completed") || + (refType.isInStateOrLater('Completed') || this.typesToIgnoreForCycles.has(refType)) ) { // that is fine @@ -249,7 +249,7 @@ export type WaitingForInvalidTypeReferencesListener = ( ) => void; export class WaitingForInvalidTypeReferences - implements TypeReferenceListener +implements TypeReferenceListener { protected counterInvalid: number; // just count the number of invalid TypeReferences @@ -267,7 +267,7 @@ export class WaitingForInvalidTypeReferences this.counterInvalid = this.waitForRefsInvalid.filter( (ref) => ref.getType() === undefined || - ref.getType()!.isInState("Invalid"), + ref.getType()!.isInState('Invalid'), ).length; // register to get updates for the relevant TypeReferences diff --git a/packages/typir/src/kinds/bottom/bottom-kind.ts b/packages/typir/src/kinds/bottom/bottom-kind.ts index c583e7a5..62fe6970 100644 --- a/packages/typir/src/kinds/bottom/bottom-kind.ts +++ b/packages/typir/src/kinds/bottom/bottom-kind.ts @@ -4,15 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { - InferCurrentTypeRule, - registerInferCurrentTypeRules, -} from "../../utils/utils-definitions.js"; -import { assertTrue } from "../../utils/utils.js"; -import { isKind, Kind } from "../kind.js"; -import { BottomType } from "./bottom-type.js"; +import type { TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule } from '../../utils/utils-definitions.js'; +import { registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; +import { assertTrue } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { BottomType } from './bottom-type.js'; export interface BottomTypeDetails extends TypeDetails { @@ -27,7 +26,7 @@ export interface BottomKindOptions { name: string; } -export const BottomKindName = "BottomKind"; +export const BottomKindName = 'BottomKind'; export interface BottomFactoryService { create( @@ -44,9 +43,9 @@ interface BottomConfigurationChain { } export class BottomKind - implements Kind, BottomFactoryService +implements Kind, BottomFactoryService { - readonly $name: "BottomKind"; + readonly $name: 'BottomKind'; readonly services: TypirServices; readonly options: Readonly; @@ -65,7 +64,7 @@ export class BottomKind ): BottomKindOptions { return { // the default values: - name: "never", + name: 'never', // the actually overriden values: ...options, }; @@ -99,7 +98,7 @@ export function isBottomKind( } class BottomConfigurationChainImpl - implements BottomConfigurationChain +implements BottomConfigurationChain { protected readonly services: TypirServices; protected readonly kind: BottomKind; diff --git a/packages/typir/src/kinds/bottom/bottom-type.ts b/packages/typir/src/kinds/bottom/bottom-type.ts index 019e9eab..752a6c64 100644 --- a/packages/typir/src/kinds/bottom/bottom-type.ts +++ b/packages/typir/src/kinds/bottom/bottom-type.ts @@ -4,12 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../../graph/type-graph.js"; -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; -import { createKindConflict } from "../../utils/utils-type-comparison.js"; -import { BottomKind, BottomTypeDetails, isBottomKind } from "./bottom-kind.js"; +import type { TypeGraphListener } from '../../graph/type-graph.js'; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; +import { createKindConflict } from '../../utils/utils-type-comparison.js'; +import type { BottomKind, BottomTypeDetails } from './bottom-kind.js'; +import { isBottomKind } from './bottom-kind.js'; export class BottomType extends Type implements TypeGraphListener { override readonly kind: BottomKind; diff --git a/packages/typir/src/kinds/class/class-initializer.ts b/packages/typir/src/kinds/class/class-initializer.ts index dfa886b0..7bdac71f 100644 --- a/packages/typir/src/kinds/class/class-initializer.ts +++ b/packages/typir/src/kinds/class/class-initializer.ts @@ -4,33 +4,36 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type, TypeStateListener } from "../../graph/type-node.js"; -import { TypeInitializer } from "../../initialization/type-initializer.js"; +import type { Type, TypeStateListener } from '../../graph/type-node.js'; +import { isType } from '../../graph/type-node.js'; +import { TypeInitializer } from '../../initialization/type-initializer.js'; +import type { TypeInferenceRule } from '../../services/inference.js'; import { InferenceProblem, InferenceRuleNotApplicable, - TypeInferenceRule, -} from "../../services/inference.js"; -import { TypirServices } from "../../typir.js"; +} from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import type { + InferenceRuleWithOptions, + ValidationRuleWithOptions, +} from '../../utils/utils-definitions.js'; import { bindInferCurrentTypeRule, bindValidateCurrentTypeRule, - InferenceRuleWithOptions, optionsBoundToType, - ValidationRuleWithOptions, -} from "../../utils/utils-definitions.js"; +} from '../../utils/utils-definitions.js'; import { checkNameTypesMap, createTypeCheckStrategy, MapListConverter, -} from "../../utils/utils-type-comparison.js"; -import { assertTypirType, toArray } from "../../utils/utils.js"; -import { +} from '../../utils/utils-type-comparison.js'; +import { assertTypirType, toArray } from '../../utils/utils.js'; +import type { ClassKind, CreateClassTypeDetails, InferClassLiteral, -} from "./class-kind.js"; -import { ClassType, isClassType } from "./class-type.js"; +} from './class-kind.js'; +import { ClassType, isClassType } from './class-type.js'; export class ClassTypeInitializer extends TypeInitializer @@ -58,7 +61,7 @@ export class ClassTypeInitializer kind as ClassKind, typeDetails as CreateClassTypeDetails, ); - if (kind.options.typing === "Structural") { + if (kind.options.typing === 'Structural') { // register structural classes also by their names, since these names are usually used for reference in the DSL/AST! this.services.infrastructure.Graph.addNode( this.initialClassType, @@ -101,7 +104,7 @@ export class ClassTypeInitializer // the class type changed, since the same type was already created earlier and is reused here (this is a special case) => skip the classType! classType.removeListener(this); // since this ClassTypeInitializer initialized the invalid type, there is nothing to do anymore here! - if (this.kind.options.typing === "Structural") { + if (this.kind.options.typing === 'Structural') { // replace the type in the type graph const nameBasedIdentifier = this.kind.calculateIdentifierWithClassNameOnly( @@ -268,7 +271,7 @@ export class ClassTypeInitializer inferenceRulesForFieldAccess.field(languageNode); if (result === InferenceRuleNotApplicable) { return InferenceRuleNotApplicable; - } else if (typeof result === "string") { + } else if (typeof result === 'string') { // get the type of the given field name const fieldType = classType.getFields(true).get(result); if (fieldType) { @@ -321,7 +324,7 @@ export class ClassTypeInitializer return; } const fieldType = - typeof field === "string" + typeof field === 'string' ? classType.getFields(true).get(field) : typir.Inference.inferType(field); if (isType(fieldType) === false) { @@ -396,7 +399,7 @@ export class ClassTypeInitializer $problem: InferenceProblem, languageNode: languageNode, inferenceCandidate: classType, - location: "values for fields", + location: 'values for fields', rule: this as unknown as TypeInferenceRule, subProblems: checkedFieldsProblems, }; diff --git a/packages/typir/src/kinds/class/class-kind.ts b/packages/typir/src/kinds/class/class-kind.ts index 7ea9dec6..b01992d5 100644 --- a/packages/typir/src/kinds/class/class-kind.ts +++ b/packages/typir/src/kinds/class/class-kind.ts @@ -4,39 +4,43 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from "langium"; -import { Type, TypeDetails } from "../../graph/type-node.js"; -import { TypeInitializer } from "../../initialization/type-initializer.js"; -import { TypeReference } from "../../initialization/type-reference.js"; -import { TypeSelector } from "../../initialization/type-selector.js"; -import { InferenceRuleNotApplicable } from "../../services/inference.js"; -import { ValidationRule } from "../../services/validation.js"; -import { TypirServices } from "../../typir.js"; -import { +import { assertUnreachable } from 'langium'; +import type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { TypeInitializer } from '../../initialization/type-initializer.js'; +import { TypeReference } from '../../initialization/type-reference.js'; +import type { TypeSelector } from '../../initialization/type-selector.js'; +import type { InferenceRuleNotApplicable } from '../../services/inference.js'; +import type { ValidationRule } from '../../services/validation.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule, RegistrationOptions, -} from "../../utils/utils-definitions.js"; -import { TypeCheckStrategy } from "../../utils/utils-type-comparison.js"; -import { assertTrue, assertTypirType, toArray } from "../../utils/utils.js"; -import { FunctionType } from "../function/function-type.js"; -import { Kind, isKind } from "../kind.js"; -import { ClassTypeInitializer } from "./class-initializer.js"; -import { ClassType, isClassType } from "./class-type.js"; -import { +} from '../../utils/utils-definitions.js'; +import type { TypeCheckStrategy } from '../../utils/utils-type-comparison.js'; +import { assertTrue, assertTypirType, toArray } from '../../utils/utils.js'; +import type { FunctionType } from '../function/function-type.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { ClassTypeInitializer } from './class-initializer.js'; +import type { ClassType } from './class-type.js'; +import { isClassType } from './class-type.js'; +import type { NoSuperClassCyclesValidationOptions, + UniqueMethodValidationOptions, +} from './class-validation.js'; +import { UniqueClassValidation, UniqueMethodValidation, - UniqueMethodValidationOptions, createNoSuperClassCyclesValidation, -} from "./class-validation.js"; +} from './class-validation.js'; import { TopClassKind, TopClassKindName, isTopClassKind, -} from "./top-class-kind.js"; +} from './top-class-kind.js'; export interface ClassKindOptions { - typing: "Structural" | "Nominal"; // JS classes are nominal, TS structures are structural + typing: 'Structural' | 'Nominal'; // JS classes are nominal, TS structures are structural /** Values < 0 indicate an arbitrary number of super classes. */ maximumNumberOfSuperClasses: number; subtypeFieldChecking: TypeCheckStrategy; @@ -44,7 +48,7 @@ export interface ClassKindOptions { identifierPrefix: string; } -export const ClassKindName = "ClassKind"; +export const ClassKindName = 'ClassKind'; export interface CreateFieldDetails { name: string; @@ -145,9 +149,9 @@ export interface ClassConfigurationChain { * The order of fields is not defined, i.e. there is no order of fields. */ export class ClassKind - implements Kind, ClassFactoryService +implements Kind, ClassFactoryService { - readonly $name: "ClassKind"; + readonly $name: 'ClassKind'; readonly services: TypirServices; readonly options: Readonly; @@ -167,10 +171,10 @@ export class ClassKind ): ClassKindOptions { return { // the default values: - typing: "Nominal", + typing: 'Nominal', maximumNumberOfSuperClasses: 1, - subtypeFieldChecking: "EQUAL_TYPE", - identifierPrefix: "class", + subtypeFieldChecking: 'EQUAL_TYPE', + identifierPrefix: 'class', // the actually overriden values: ...options, }; @@ -185,7 +189,7 @@ export class ClassKind typeDetails: ClassTypeDetails | string, ): TypeReference { // string for nominal typing - if (typeof typeDetails === "string") { + if (typeof typeDetails === 'string') { // nominal typing return new TypeReference( typeDetails, @@ -219,8 +223,8 @@ export class ClassKind protected getIdentifierPrefix(): string { return this.options.identifierPrefix - ? this.options.identifierPrefix + "-" - : ""; + ? this.options.identifierPrefix + '-' + : ''; } /** @@ -238,7 +242,7 @@ export class ClassKind */ calculateIdentifier(typeDetails: ClassTypeDetails): string { // purpose of identifier: distinguish different types; NOT: not uniquely overloaded types - if (this.options.typing === "Structural") { + if (this.options.typing === 'Structural') { // fields const fields: string = typeDetails.fields .map( @@ -246,7 +250,7 @@ export class ClassKind `${f.name}:${this.services.infrastructure.TypeResolver.resolve(f.type).getIdentifier()}`, ) // the names and the types of the fields are relevant, since different field types lead to different class types! .sort() // the order of fields does not matter, therefore we need a stable order to make the identifiers comparable - .join(","); + .join(','); // methods const methods: string = typeDetails.methods .map((m) => @@ -255,7 +259,7 @@ export class ClassKind ).getIdentifier(), ) .sort() // the order of methods does not matter, therefore we need a stable order to make the identifiers comparable - .join(","); + .join(','); // super classes (TODO oder strukturell per getAllSuperClassX lösen?!) const superClasses: string = toArray(typeDetails.superClasses) .map((selector) => { @@ -267,10 +271,10 @@ export class ClassKind return type.getIdentifier(); }) .sort() - .join(","); + .join(','); // complete identifier (the name of the class does not matter for structural typing!) return `${this.getIdentifierPrefix()}fields{${fields}}-methods{${methods}}-extends{${superClasses}}`; - } else if (this.options.typing === "Nominal") { + } else if (this.options.typing === 'Nominal') { // only the name of the class matters for nominal typing! return this.calculateIdentifierWithClassNameOnly(typeDetails); } else { @@ -303,7 +307,7 @@ export class ClassKind options: RegistrationOptions, ): UniqueClassValidation { const rule = new UniqueClassValidation(this.services); - if (options.registration === "MYSELF") { + if (options.registration === 'MYSELF') { // do nothing, the user is responsible to register the rule } else { this.services.validation.Collector.addValidationRule( @@ -322,7 +326,7 @@ export class ClassKind this.services, options, ); - if (options.registration === "MYSELF") { + if (options.registration === 'MYSELF') { // do nothing, the user is responsible to register the rule } else { this.services.validation.Collector.addValidationRule( @@ -338,7 +342,7 @@ export class ClassKind RegistrationOptions, ): ValidationRule { const rule = createNoSuperClassCyclesValidation(options); - if (options.registration === "MYSELF") { + if (options.registration === 'MYSELF') { // do nothing, the user is responsible to register the rule } else { this.services.validation.Collector.addValidationRule( @@ -357,7 +361,7 @@ export function isClassKind( } class ClassConfigurationChainImpl - implements ClassConfigurationChain +implements ClassConfigurationChain { protected readonly services: TypirServices; protected readonly kind: ClassKind; diff --git a/packages/typir/src/kinds/class/class-type.ts b/packages/typir/src/kinds/class/class-type.ts index 8907f58f..e35e199b 100644 --- a/packages/typir/src/kinds/class/class-type.ts +++ b/packages/typir/src/kinds/class/class-type.ts @@ -4,24 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../../graph/type-node.js"; -import { TypeReference } from "../../initialization/type-reference.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeReference } from '../../initialization/type-reference.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; import { checkNameTypesMap, checkValueForConflict, createKindConflict, createTypeCheckStrategy, IndexedTypeConflict, -} from "../../utils/utils-type-comparison.js"; +} from '../../utils/utils-type-comparison.js'; import { assertUnreachable, removeFromArray, toArray, -} from "../../utils/utils.js"; -import { FunctionType } from "../function/function-type.js"; -import { ClassKind, ClassTypeDetails, isClassKind } from "./class-kind.js"; +} from '../../utils/utils.js'; +import type { FunctionType } from '../function/function-type.js'; +import type { ClassKind, ClassTypeDetails } from './class-kind.js'; +import { isClassKind } from './class-kind.js'; export interface FieldDetails { name: string; @@ -53,7 +54,7 @@ export class ClassType extends Type { typeDetails: ClassTypeDetails, ) { super( - kind.options.typing === "Nominal" + kind.options.typing === 'Nominal' ? kind.calculateIdentifierWithClassNameOnly(typeDetails) // use the name of the class as identifier already now : undefined, // the identifier for structurally typed classes will be set later after resolving all fields and methods typeDetails, @@ -188,7 +189,7 @@ export class ClassType extends Type { fields.push(`${field[0]}: ${field[1].getName()}`); } if (fields.length >= 1) { - slots.push(fields.join(", ")); + slots.push(fields.join(', ')); } // methods const methods: string[] = []; @@ -196,21 +197,21 @@ export class ClassType extends Type { methods.push(`${method.getUserRepresentation()}`); } if (methods.length >= 1) { - slots.push(methods.join(", ")); + slots.push(methods.join(', ')); } // super classes const superClasses = this.getDeclaredSuperClasses(); const extendedClasses = superClasses.length <= 0 - ? "" - : ` extends ${superClasses.map((c) => c.getName()).join(", ")}`; + ? '' + : ` extends ${superClasses.map((c) => c.getName()).join(', ')}`; // complete representation - return `${this.className}${extendedClasses} { ${slots.join(", ")} }`; + return `${this.className}${extendedClasses} { ${slots.join(', ')} }`; } override analyzeTypeEqualityProblems(otherType: Type): TypirProblem[] { if (isClassType(otherType)) { - if (this.kind.options.typing === "Structural") { + if (this.kind.options.typing === 'Structural') { // for structural typing: return checkNameTypesMap( this.getFields(true), @@ -221,12 +222,12 @@ export class ClassType extends Type { t2, ), ); - } else if (this.kind.options.typing === "Nominal") { + } else if (this.kind.options.typing === 'Nominal') { // for nominal typing: return checkValueForConflict( this.getIdentifier(), otherType.getIdentifier(), - "name", + 'name', ); } else { assertUnreachable(this.kind.options.typing); @@ -247,7 +248,7 @@ export class ClassType extends Type { subType: ClassType, superType: ClassType, ): TypirProblem[] { - if (this.kind.options.typing === "Structural") { + if (this.kind.options.typing === 'Structural') { // for structural typing, the sub type needs to have all fields of the super type with assignable types (including fields of all super classes): const conflicts: IndexedTypeConflict[] = []; const subFields = subType.getFields(true); @@ -289,7 +290,7 @@ export class ClassType extends Type { } // Note that it is not necessary to check, whether the sub class has additional fields than the super type! return conflicts; - } else if (this.kind.options.typing === "Nominal") { + } else if (this.kind.options.typing === 'Nominal') { // for nominal typing (takes super classes into account) const allSub = subType.getAllSuperClasses(true); const globalResult: TypirProblem[] = []; @@ -316,7 +317,7 @@ export class ClassType extends Type { if (superType) { return superType; } else { - throw new Error("Not all super class types are resolved."); + throw new Error('Not all super class types are resolved.'); } }); } @@ -377,7 +378,7 @@ export class ClassType extends Type { ensureNoCycles(): void { if (this.hasSubSuperClassCycles()) { throw new Error( - "This is not possible, since this class has cycles in its super-classes!", + 'This is not possible, since this class has cycles in its super-classes!', ); } } @@ -402,7 +403,7 @@ export class ClassType extends Type { if (field) { result.set(fieldDetails.name, field); } else { - throw new Error("Not all fields are resolved."); + throw new Error('Not all fields are resolved.'); } }); return result; @@ -415,7 +416,7 @@ export class ClassType extends Type { if (method) { return method; } else { - throw new Error("Not all methods are resolved."); + throw new Error('Not all methods are resolved.'); } }); // methods of super classes diff --git a/packages/typir/src/kinds/class/class-validation.ts b/packages/typir/src/kinds/class/class-validation.ts index 4b15537c..8db8632e 100644 --- a/packages/typir/src/kinds/class/class-validation.ts +++ b/packages/typir/src/kinds/class/class-validation.ts @@ -4,21 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { ValidationProblemAcceptor, ValidationRule, ValidationRuleLifecycle, -} from "../../services/validation.js"; -import { TypirServices } from "../../typir.js"; -import { FunctionType, isFunctionType } from "../function/function-type.js"; -import { ClassType, isClassType } from "./class-type.js"; +} from '../../services/validation.js'; +import type { TypirServices } from '../../typir.js'; +import type { FunctionType } from '../function/function-type.js'; +import { isFunctionType } from '../function/function-type.js'; +import type { ClassType } from './class-type.js'; +import { isClassType } from './class-type.js'; /** * Predefined validation to produce errors, if the same class is declared more than once. * This is often relevant for nominally typed classes. */ export class UniqueClassValidation - implements ValidationRuleLifecycle +implements ValidationRuleLifecycle { protected readonly foundDeclarations: Map = new Map(); @@ -87,7 +89,7 @@ export class UniqueClassValidation for (const clas of classes) { accept({ languageNode: clas, - severity: "error", + severity: 'error', message: `Declared classes need to be unique (${key}).`, }); } @@ -227,7 +229,7 @@ export class UniqueMethodValidation< } else { accept({ languageNode: method.languageNode, - severity: "error", + severity: 'error', message: `Declared methods need to be unique (${key}).`, }); } @@ -263,13 +265,13 @@ export function createNoSuperClassCyclesValidation( const classType = typir.Inference.inferType(languageNode); if ( isClassType(classType) && - classType.isInStateOrLater("Completed") + classType.isInStateOrLater('Completed') ) { // check for cycles in sub-type-relationships if (classType.hasSubSuperClassCycles()) { accept({ languageNode: languageNode, - severity: "error", + severity: 'error', message: `Cycles in super-sub-class-relationships are not allowed: ${classType.getName()}`, }); } diff --git a/packages/typir/src/kinds/class/top-class-kind.ts b/packages/typir/src/kinds/class/top-class-kind.ts index 9e6942b4..a7bd795a 100644 --- a/packages/typir/src/kinds/class/top-class-kind.ts +++ b/packages/typir/src/kinds/class/top-class-kind.ts @@ -4,15 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { - InferCurrentTypeRule, - registerInferCurrentTypeRules, -} from "../../utils/utils-definitions.js"; -import { assertTrue } from "../../utils/utils.js"; -import { isKind, Kind } from "../kind.js"; -import { TopClassType } from "./top-class-type.js"; +import type { TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule } from '../../utils/utils-definitions.js'; +import { registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; +import { assertTrue } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { TopClassType } from './top-class-type.js'; export interface TopClassTypeDetails extends TypeDetails { @@ -25,10 +24,10 @@ export interface TopClassKindOptions { name: string; } -export const TopClassKindName = "TopClassKind"; +export const TopClassKindName = 'TopClassKind'; export class TopClassKind implements Kind { - readonly $name: "TopClassKind"; + readonly $name: 'TopClassKind'; readonly services: TypirServices; readonly options: TopClassKindOptions; protected instance: TopClassType | undefined; @@ -48,7 +47,7 @@ export class TopClassKind implements Kind { ): TopClassKindOptions { return { // the default values: - name: "TopClass", + name: 'TopClass', // the actually overriden values: ...options, }; diff --git a/packages/typir/src/kinds/class/top-class-type.ts b/packages/typir/src/kinds/class/top-class-type.ts index 3fe8b10b..068aa1c2 100644 --- a/packages/typir/src/kinds/class/top-class-type.ts +++ b/packages/typir/src/kinds/class/top-class-type.ts @@ -4,17 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../../graph/type-graph.js"; -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; -import { createKindConflict } from "../../utils/utils-type-comparison.js"; -import { isClassType } from "./class-type.js"; -import { - isTopClassKind, - TopClassKind, - TopClassTypeDetails, -} from "./top-class-kind.js"; +import type { TypeGraphListener } from '../../graph/type-graph.js'; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; +import { createKindConflict } from '../../utils/utils-type-comparison.js'; +import { isClassType } from './class-type.js'; +import type { TopClassKind, TopClassTypeDetails } from './top-class-kind.js'; +import { isTopClassKind } from './top-class-kind.js'; export class TopClassType extends Type implements TypeGraphListener { override readonly kind: TopClassKind; diff --git a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts index 8043d031..b4a4200b 100644 --- a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts +++ b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-kind.ts @@ -4,12 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { TypeCheckStrategy } from "../../utils/utils-type-comparison.js"; -import { assertTrue, toArray } from "../../utils/utils.js"; -import { Kind, isKind } from "../kind.js"; -import { FixedParameterType } from "./fixed-parameters-type.js"; +import type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import type { TypeCheckStrategy } from '../../utils/utils-type-comparison.js'; +import { assertTrue, toArray } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { FixedParameterType } from './fixed-parameters-type.js'; export class Parameter { readonly name: string; @@ -30,7 +31,7 @@ export interface FixedParameterKindOptions { parameterSubtypeCheckingStrategy: TypeCheckStrategy; } -export const FixedParameterKindName = "FixedParameterKind"; +export const FixedParameterKindName = 'FixedParameterKind'; /** * Suitable for kinds like Collection, List, Array, Map, ..., i.e. types with a fixed number of arbitrary parameter types @@ -66,7 +67,7 @@ export class FixedParameterKind implements Kind { ): FixedParameterKindOptions { return { // the default values: - parameterSubtypeCheckingStrategy: "EQUAL_TYPE", + parameterSubtypeCheckingStrategy: 'EQUAL_TYPE', // the actually overriden values: ...options, }; @@ -113,7 +114,7 @@ export class FixedParameterKind implements Kind { return this.printSignature( this.baseName, toArray(typeDetails.parameterTypes), - ",", + ',', ); // use the signature for a unique name } @@ -129,5 +130,5 @@ export class FixedParameterKind implements Kind { export function isFixedParametersKind( kind: unknown, ): kind is FixedParameterKind { - return isKind(kind) && kind.$name.startsWith("FixedParameterKind-"); + return isKind(kind) && kind.$name.startsWith('FixedParameterKind-'); } diff --git a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts index 81f8c7e1..fcd19484 100644 --- a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts +++ b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts @@ -4,22 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; import { checkTypeArrays, checkValueForConflict, createKindConflict, createTypeCheckStrategy, -} from "../../utils/utils-type-comparison.js"; -import { assertTrue, toArray } from "../../utils/utils.js"; -import { +} from '../../utils/utils-type-comparison.js'; +import { assertTrue, toArray } from '../../utils/utils.js'; +import type { FixedParameterKind, FixedParameterTypeDetails, - isFixedParametersKind, Parameter, -} from "./fixed-parameters-kind.js"; +} from './fixed-parameters-kind.js'; +import { isFixedParametersKind } from './fixed-parameters-kind.js'; export class ParameterValue { readonly parameter: Parameter; @@ -60,7 +60,7 @@ export class FixedParameterType extends Type { } override getName(): string { - return `${this.kind.printSignature(this.kind.baseName, this.getParameterTypes(), ", ")}`; + return `${this.kind.printSignature(this.kind.baseName, this.getParameterTypes(), ', ')}`; } override getUserRepresentation(): string { @@ -73,7 +73,7 @@ export class FixedParameterType extends Type { const baseTypeCheck = checkValueForConflict( this.kind.baseName, otherType.kind.baseName, - "base type", + 'base type', ); if (baseTypeCheck.length >= 1) { // e.g. List !== Set @@ -115,7 +115,7 @@ export class FixedParameterType extends Type { const baseTypeCheck = checkValueForConflict( subType.kind.baseName, superType.kind.baseName, - "base type", + 'base type', ); if (baseTypeCheck.length >= 1) { // e.g. List !== Set diff --git a/packages/typir/src/kinds/function/function-inference-call.ts b/packages/typir/src/kinds/function/function-inference-call.ts index 9787080b..15d24e48 100644 --- a/packages/typir/src/kinds/function/function-inference-call.ts +++ b/packages/typir/src/kinds/function/function-inference-call.ts @@ -4,22 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../../graph/type-node.js"; -import { - AssignabilitySuccess, - isAssignabilityProblem, -} from "../../services/assignability.js"; +import type { Type } from '../../graph/type-node.js'; +import type { AssignabilitySuccess } from '../../services/assignability.js'; +import { isAssignabilityProblem } from '../../services/assignability.js'; +import type { + TypeInferenceResultWithInferringChildren, + TypeInferenceRuleWithInferringChildren, +} from '../../services/inference.js'; import { InferenceProblem, InferenceRuleNotApplicable, - TypeInferenceResultWithInferringChildren, - TypeInferenceRuleWithInferringChildren, -} from "../../services/inference.js"; -import { TypirServices } from "../../typir.js"; -import { checkTypeArrays } from "../../utils/utils-type-comparison.js"; -import { FunctionTypeDetails, InferFunctionCall } from "./function-kind.js"; -import { AvailableFunctionsManager } from "./function-overloading.js"; -import { FunctionType } from "./function-type.js"; +} from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import { checkTypeArrays } from '../../utils/utils-type-comparison.js'; +import type { + FunctionTypeDetails, + InferFunctionCall, +} from './function-kind.js'; +import type { AvailableFunctionsManager } from './function-overloading.js'; +import type { FunctionType } from './function-type.js'; /** * Dedicated inference rule for calls of a single function signature. @@ -144,7 +147,7 @@ export class FunctionCallInferenceRule< $problem: InferenceProblem, languageNode: languageNode, inferenceCandidate: this.functionType, - location: "input parameters", + location: 'input parameters', rule: this, subProblems: comparisonConflicts, }; diff --git a/packages/typir/src/kinds/function/function-inference-overloaded.ts b/packages/typir/src/kinds/function/function-inference-overloaded.ts index c2786622..b01b4462 100644 --- a/packages/typir/src/kinds/function/function-inference-overloaded.ts +++ b/packages/typir/src/kinds/function/function-inference-overloaded.ts @@ -4,15 +4,15 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../../graph/type-node.js"; -import { isConversionEdge } from "../../services/conversion.js"; +import type { Type } from '../../graph/type-node.js'; +import { isConversionEdge } from '../../services/conversion.js'; import { CompositeTypeInferenceRule, InferenceProblem, -} from "../../services/inference.js"; -import { isSubTypeEdge } from "../../services/subtype.js"; -import { assertUnreachable } from "../../utils/utils.js"; -import { FunctionCallInferenceRule } from "./function-inference-call.js"; +} from '../../services/inference.js'; +import { isSubTypeEdge } from '../../services/subtype.js'; +import { assertUnreachable } from '../../utils/utils.js'; +import type { FunctionCallInferenceRule } from './function-inference-call.js'; /** * Custom inference rule for functions, which consists of one inference rule for each overload/signature for a function with same name. @@ -83,7 +83,7 @@ export class OverloadedFunctionsTypeInferenceRule< collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: "found no applicable inference rules", + location: 'found no applicable inference rules', subProblems: [], }); } @@ -131,7 +131,7 @@ export class OverloadedFunctionsTypeInferenceRule< { $problem: InferenceProblem, languageNode: languageNode, - location: `Found ${bestMatches.length} best matching overloads: ${bestMatches.map((m) => m.result.getIdentifier()).join(", ")}`, + location: `Found ${bestMatches.length} best matching overloads: ${bestMatches.map((m) => m.result.getIdentifier()).join(', ')}`, subProblems: [], // there are no real sub-problems, since the relevant overloads match ... }, ]; @@ -166,8 +166,8 @@ export class OverloadedFunctionsTypeInferenceRule< (isSubTypeEdge(edge) ? 1 : isConversionEdge(edge) - ? 2 - : assertUnreachable(edge)) as number, + ? 2 + : assertUnreachable(edge)) as number, ) .reduce((l, r) => l + r, 0) ); // sum of all costs diff --git a/packages/typir/src/kinds/function/function-initializer.ts b/packages/typir/src/kinds/function/function-initializer.ts index 9afb62fc..12cc12e1 100644 --- a/packages/typir/src/kinds/function/function-initializer.ts +++ b/packages/typir/src/kinds/function/function-initializer.ts @@ -4,25 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeStateListener } from "../../graph/type-node.js"; -import { TypeInitializer } from "../../initialization/type-initializer.js"; -import { TypeInferenceRule } from "../../services/inference.js"; -import { TypirServices } from "../../typir.js"; +import type { Type, TypeStateListener } from '../../graph/type-node.js'; +import { TypeInitializer } from '../../initialization/type-initializer.js'; +import type { TypeInferenceRule } from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferenceRuleWithOptions } from '../../utils/utils-definitions.js'; import { bindInferCurrentTypeRule, - InferenceRuleWithOptions, optionsBoundToType, -} from "../../utils/utils-definitions.js"; -import { assertTypirType } from "../../utils/utils.js"; -import { FunctionCallInferenceRule } from "./function-inference-call.js"; -import { +} from '../../utils/utils-definitions.js'; +import { assertTypirType } from '../../utils/utils.js'; +import { FunctionCallInferenceRule } from './function-inference-call.js'; +import type { CreateFunctionTypeDetails, FunctionKind, FunctionTypeDetails, InferFunctionCall, -} from "./function-kind.js"; -import { AvailableFunctionsManager } from "./function-overloading.js"; -import { FunctionType, isFunctionType } from "./function-type.js"; +} from './function-kind.js'; +import type { AvailableFunctionsManager } from './function-overloading.js'; +import { FunctionType, isFunctionType } from './function-type.js'; /** * For each call of FunctionKind.create()...finish(), one instance of this class will be created, diff --git a/packages/typir/src/kinds/function/function-kind.ts b/packages/typir/src/kinds/function/function-kind.ts index 785b8bfc..24ab2872 100644 --- a/packages/typir/src/kinds/function/function-kind.ts +++ b/packages/typir/src/kinds/function/function-kind.ts @@ -4,23 +4,24 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from "../../graph/type-node.js"; -import { TypeInitializer } from "../../initialization/type-initializer.js"; -import { TypeReference } from "../../initialization/type-reference.js"; -import { TypeSelector } from "../../initialization/type-selector.js"; -import { ValidationRule } from "../../services/validation.js"; -import { TypirServices } from "../../typir.js"; -import { +import type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { TypeInitializer } from '../../initialization/type-initializer.js'; +import { TypeReference } from '../../initialization/type-reference.js'; +import type { TypeSelector } from '../../initialization/type-selector.js'; +import type { ValidationRule } from '../../services/validation.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule, NameTypePair, RegistrationOptions, -} from "../../utils/utils-definitions.js"; -import { TypeCheckStrategy } from "../../utils/utils-type-comparison.js"; -import { isKind, Kind } from "../kind.js"; -import { FunctionTypeInitializer } from "./function-initializer.js"; -import { AvailableFunctionsManager } from "./function-overloading.js"; -import { FunctionType } from "./function-type.js"; -import { UniqueFunctionValidation } from "./function-validation-unique.js"; +} from '../../utils/utils-definitions.js'; +import type { TypeCheckStrategy } from '../../utils/utils-type-comparison.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { FunctionTypeInitializer } from './function-initializer.js'; +import { AvailableFunctionsManager } from './function-overloading.js'; +import type { FunctionType } from './function-type.js'; +import { UniqueFunctionValidation } from './function-validation-unique.js'; export interface FunctionKindOptions { // these three options controls structural vs nominal typing somehow ... @@ -32,12 +33,12 @@ export interface FunctionKindOptions { /** If a function has no output type (e.g. "void" functions), this type is returned during the type inference of calls to these functions. * The default value "THROW_ERROR" indicates to throw an error, i.e. type inference for calls of such functions are not allowed. */ typeToInferForCallsOfFunctionsWithoutOutput: - | "THROW_ERROR" + | 'THROW_ERROR' | TypeSelector; subtypeParameterChecking: TypeCheckStrategy; } -export const FunctionKindName = "FunctionKind"; +export const FunctionKindName = 'FunctionKind'; export interface CreateParameterDetails { name: string; @@ -169,9 +170,9 @@ export interface FunctionConfigurationChain { * - parameters which are used for output AND input */ export class FunctionKind - implements Kind, FunctionFactoryService +implements Kind, FunctionFactoryService { - readonly $name: "FunctionKind"; + readonly $name: 'FunctionKind'; readonly services: TypirServices; readonly options: Readonly>; readonly functions: AvailableFunctionsManager; @@ -195,9 +196,9 @@ export class FunctionKind enforceFunctionName: false, enforceInputParameterNames: false, enforceOutputParameterName: false, - identifierPrefix: "function", - typeToInferForCallsOfFunctionsWithoutOutput: "THROW_ERROR", - subtypeParameterChecking: "SUB_TYPE", + identifierPrefix: 'function', + typeToInferForCallsOfFunctionsWithoutOutput: 'THROW_ERROR', + subtypeParameterChecking: 'SUB_TYPE', // the actually overriden values: ...options, }; @@ -230,15 +231,15 @@ export class FunctionKind functionType: FunctionType, ): Type | undefined { return ( - functionType.getOutput("RETURN_UNDEFINED")?.type ?? // by default, use the return type of the function ... + functionType.getOutput('RETURN_UNDEFINED')?.type ?? // by default, use the return type of the function ... // ... if this type is missing, use the specified type for this case in the options: // 'THROW_ERROR': an error will be thrown later, when this case actually occurs! (this.options.typeToInferForCallsOfFunctionsWithoutOutput === - "THROW_ERROR" + 'THROW_ERROR' ? undefined : this.services.infrastructure.TypeResolver.resolve( - this.options.typeToInferForCallsOfFunctionsWithoutOutput, - )) + this.options.typeToInferForCallsOfFunctionsWithoutOutput, + )) ); } @@ -246,12 +247,12 @@ export class FunctionKind typeDetails: FunctionTypeDetails, ): string { const prefix = this.options.identifierPrefix - ? this.options.identifierPrefix + "-" - : ""; + ? this.options.identifierPrefix + '-' + : ''; // function name, if wanted const functionName = this.hasFunctionName(typeDetails.functionName) ? typeDetails.functionName - : ""; + : ''; // inputs: type identifiers in defined order const inputsString = typeDetails.inputParameters .map((input) => @@ -259,13 +260,13 @@ export class FunctionKind input.type, ).getIdentifier(), ) - .join(","); + .join(','); // output: type identifier const outputString = typeDetails.outputParameter ? this.services.infrastructure.TypeResolver.resolve( - typeDetails.outputParameter.type, - ).getIdentifier() - : ""; + typeDetails.outputParameter.type, + ).getIdentifier() + : ''; // complete signature return `${prefix}${functionName}(${inputsString}):${outputString}`; } @@ -281,7 +282,7 @@ export class FunctionKind enforceFunctionName(name: string | undefined, enforce: boolean): void { if (enforce && this.hasFunctionName(name) === false) { - throw new Error("A name for the function is required."); + throw new Error('A name for the function is required.'); } } hasFunctionName(name: string | undefined): name is string { @@ -290,7 +291,7 @@ export class FunctionKind enforceParameterName(name: string | undefined, enforce: boolean): void { if (enforce && this.hasParameterName(name) === false) { - throw new Error("A name for the parameter is required."); + throw new Error('A name for the parameter is required.'); } } hasParameterName(name: string | undefined): name is string { @@ -301,7 +302,7 @@ export class FunctionKind options: RegistrationOptions, ): ValidationRule { const rule = new UniqueFunctionValidation(this.services); - if (options.registration === "MYSELF") { + if (options.registration === 'MYSELF') { // do nothing, the user is responsible to register the rule } else { this.services.validation.Collector.addValidationRule( @@ -320,7 +321,7 @@ export function isFunctionKind( } class FunctionConfigurationChainImpl - implements FunctionConfigurationChain +implements FunctionConfigurationChain { protected readonly services: TypirServices; protected readonly kind: FunctionKind; @@ -368,5 +369,5 @@ class FunctionConfigurationChainImpl } // when the name is missing (e.g. for functions or their input/output parameters), use these values instead -export const NO_FUNCTION_NAME = ""; -export const NO_PARAMETER_NAME = ""; +export const NO_FUNCTION_NAME = ''; +export const NO_PARAMETER_NAME = ''; diff --git a/packages/typir/src/kinds/function/function-overloading.ts b/packages/typir/src/kinds/function/function-overloading.ts index 5ce56c4a..a4d65ece 100644 --- a/packages/typir/src/kinds/function/function-overloading.ts +++ b/packages/typir/src/kinds/function/function-overloading.ts @@ -4,16 +4,17 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../../graph/type-graph.js"; -import { Type } from "../../graph/type-node.js"; -import { CompositeTypeInferenceRule } from "../../services/inference.js"; -import { TypirServices } from "../../typir.js"; -import { RuleRegistry } from "../../utils/rule-registration.js"; -import { removeFromArray } from "../../utils/utils.js"; -import { OverloadedFunctionsTypeInferenceRule } from "./function-inference-overloaded.js"; -import { FunctionKind, InferFunctionCall } from "./function-kind.js"; -import { FunctionType, isFunctionType } from "./function-type.js"; -import { FunctionCallArgumentsValidation } from "./function-validation-calls.js"; +import type { TypeGraphListener } from '../../graph/type-graph.js'; +import type { Type } from '../../graph/type-node.js'; +import type { CompositeTypeInferenceRule } from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import { RuleRegistry } from '../../utils/rule-registration.js'; +import { removeFromArray } from '../../utils/utils.js'; +import { OverloadedFunctionsTypeInferenceRule } from './function-inference-overloaded.js'; +import type { FunctionKind, InferFunctionCall } from './function-kind.js'; +import type { FunctionType } from './function-type.js'; +import { isFunctionType } from './function-type.js'; +import { FunctionCallArgumentsValidation } from './function-validation-calls.js'; /** * Collects information about all functions with the same name. @@ -44,7 +45,7 @@ export interface SingleFunctionDetails< * In each type system, exactly one instance of this class is stored by the FunctionKind. */ export class AvailableFunctionsManager - implements TypeGraphListener +implements TypeGraphListener { protected readonly services: TypirServices; protected readonly kind: FunctionKind; diff --git a/packages/typir/src/kinds/function/function-type.ts b/packages/typir/src/kinds/function/function-type.ts index 2c607c16..c968bcde 100644 --- a/packages/typir/src/kinds/function/function-type.ts +++ b/packages/typir/src/kinds/function/function-type.ts @@ -4,23 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, isType } from "../../graph/type-node.js"; -import { TypeReference } from "../../initialization/type-reference.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { NameTypePair, TypirProblem } from "../../utils/utils-definitions.js"; +import { Type, isType } from '../../graph/type-node.js'; +import { TypeReference } from '../../initialization/type-reference.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { + NameTypePair, + TypirProblem, +} from '../../utils/utils-definitions.js'; import { checkTypeArrays, checkTypes, checkValueForConflict, createKindConflict, createTypeCheckStrategy, -} from "../../utils/utils-type-comparison.js"; -import { assertTrue, assertUnreachable } from "../../utils/utils.js"; -import { - FunctionKind, - FunctionTypeDetails, - isFunctionKind, -} from "./function-kind.js"; +} from '../../utils/utils-type-comparison.js'; +import { assertTrue, assertUnreachable } from '../../utils/utils.js'; +import type { FunctionKind, FunctionTypeDetails } from './function-kind.js'; +import { isFunctionKind } from './function-kind.js'; export interface ParameterDetails { name: string; @@ -45,9 +45,9 @@ export class FunctionType extends Type { // output parameter const outputType = typeDetails.outputParameter ? new TypeReference( - typeDetails.outputParameter.type, - this.kind.services, - ) + typeDetails.outputParameter.type, + this.kind.services, + ) : undefined; if (typeDetails.outputParameter) { assertTrue(outputType !== undefined); @@ -111,7 +111,7 @@ export class FunctionType extends Type { const inputs = this.getInputs(); const inputsString = inputs .map((input) => this.kind.getParameterRepresentation(input)) - .join(", "); + .join(', '); // output const output = this.getOutput(); const outputString = output @@ -121,10 +121,10 @@ export class FunctionType extends Type { : undefined; // complete signature if (this.kind.hasFunctionName(simpleFunctionName)) { - const outputValue = outputString ? `: ${outputString}` : ""; + const outputValue = outputString ? `: ${outputString}` : ''; return `${simpleFunctionName}(${inputsString})${outputValue}`; } else { - return `(${inputsString}) => ${outputString ?? "()"}`; + return `(${inputsString}) => ${outputString ?? '()'}`; } } @@ -137,7 +137,7 @@ export class FunctionType extends Type { ...checkValueForConflict( this.getSimpleFunctionName(), otherType.getSimpleFunctionName(), - "simple name", + 'simple name', ), ); } @@ -215,7 +215,7 @@ export class FunctionType extends Type { } getOutput( - notResolvedBehavior: "EXCEPTION" | "RETURN_UNDEFINED" = "EXCEPTION", + notResolvedBehavior: 'EXCEPTION' | 'RETURN_UNDEFINED' = 'EXCEPTION', ): NameTypePair | undefined { if (this.outputParameter) { const type = this.outputParameter.type.getType(); @@ -226,11 +226,11 @@ export class FunctionType extends Type { }; } else { switch (notResolvedBehavior) { - case "EXCEPTION": + case 'EXCEPTION': throw new Error( `Output parameter ${this.outputParameter.name} is not resolved.`, ); - case "RETURN_UNDEFINED": + case 'RETURN_UNDEFINED': return undefined; default: assertUnreachable(notResolvedBehavior); diff --git a/packages/typir/src/kinds/function/function-validation-calls.ts b/packages/typir/src/kinds/function/function-validation-calls.ts index a6c389f4..cec4d2e8 100644 --- a/packages/typir/src/kinds/function/function-validation-calls.ts +++ b/packages/typir/src/kinds/function/function-validation-calls.ts @@ -4,27 +4,27 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { - ValidationProblem, +import type { ValidationProblemAcceptor, ValidationRuleLifecycle, -} from "../../services/validation.js"; -import { TypirServices } from "../../typir.js"; -import { +} from '../../services/validation.js'; +import { ValidationProblem } from '../../services/validation.js'; +import type { TypirServices } from '../../typir.js'; +import type { RuleCollectorListener, RuleOptions, -} from "../../utils/rule-registration.js"; +} from '../../utils/rule-registration.js'; import { checkTypes, checkValueForConflict, createTypeCheckStrategy, -} from "../../utils/utils-type-comparison.js"; -import { assertUnreachable, toArray } from "../../utils/utils.js"; -import { InferFunctionCall } from "./function-kind.js"; -import { +} from '../../utils/utils-type-comparison.js'; +import { assertUnreachable, toArray } from '../../utils/utils.js'; +import type { InferFunctionCall } from './function-kind.js'; +import type { AvailableFunctionsManager, SingleFunctionDetails, -} from "./function-overloading.js"; +} from './function-overloading.js'; /** * This validation uses the inference rules for all available function calls to check, whether ... @@ -33,7 +33,7 @@ import { * There is only one instance of this class for each function kind/manager. */ export class FunctionCallArgumentsValidation - implements +implements ValidationRuleLifecycle, RuleCollectorListener> { @@ -167,8 +167,8 @@ export class FunctionCallArgumentsValidation if (resultOverloaded.length >= 1) { accept({ languageNode: languageNode, - severity: "error", - message: `The given operands for the call of ${overloadedFunctions.overloadedFunctions.length >= 2 ? "the overload " : ""}'${overloadedName}' don't match.`, + severity: 'error', + message: `The given operands for the call of ${overloadedFunctions.overloadedFunctions.length >= 2 ? 'the overload ' : ''}'${overloadedName}' don't match.`, subProblems: resultOverloaded, }); } @@ -211,15 +211,15 @@ export class FunctionCallArgumentsValidation const parameterLength = checkValueForConflict( expectedParameterTypes.length, inputArguments.length, - "number of input parameter values", + 'number of input parameter values', ); if (parameterLength.length >= 1) { currentProblems.push({ $problem: ValidationProblem, languageNode: languageNode, - severity: "error", + severity: 'error', message: - "The number of given parameter values does not match the expected number of input parameters.", + 'The number of given parameter values does not match the expected number of input parameters.', subProblems: parameterLength, }); } else { @@ -233,7 +233,7 @@ export class FunctionCallArgumentsValidation const parameterProblems = checkTypes( inferredType, expectedType, - createTypeCheckStrategy("ASSIGNABLE_TYPE", this.services), + createTypeCheckStrategy('ASSIGNABLE_TYPE', this.services), true, ); if (parameterProblems.length >= 1) { @@ -242,7 +242,7 @@ export class FunctionCallArgumentsValidation currentProblems.push({ $problem: ValidationProblem, languageNode: inputArguments[i], - severity: "error", + severity: 'error', message: `The parameter '${expectedType.name}' at index ${i} got a value with a wrong type.`, subProblems: parameterProblems, }); @@ -264,7 +264,7 @@ export class FunctionCallArgumentsValidation resultOverloaded.push({ $problem: ValidationProblem, languageNode: languageNode, - severity: "error", + severity: 'error', message: `The given arguments don't match the parameters of '${this.services.Printer.printTypeUserRepresentation(functionType)}'.`, subProblems: currentProblems, }); @@ -283,10 +283,10 @@ export class FunctionCallArgumentsValidation ): boolean { if (rule.validateArgumentsOfFunctionCalls === undefined) { return false; // the default value - } else if (typeof rule.validateArgumentsOfFunctionCalls === "boolean") { + } else if (typeof rule.validateArgumentsOfFunctionCalls === 'boolean') { return rule.validateArgumentsOfFunctionCalls; } else if ( - typeof rule.validateArgumentsOfFunctionCalls === "function" + typeof rule.validateArgumentsOfFunctionCalls === 'function' ) { return rule.validateArgumentsOfFunctionCalls(languageNode); } else { diff --git a/packages/typir/src/kinds/function/function-validation-unique.ts b/packages/typir/src/kinds/function/function-validation-unique.ts index dde0f7ea..cc9d01b4 100644 --- a/packages/typir/src/kinds/function/function-validation-unique.ts +++ b/packages/typir/src/kinds/function/function-validation-unique.ts @@ -4,19 +4,20 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { +import type { ValidationProblemAcceptor, ValidationRuleLifecycle, -} from "../../services/validation.js"; -import { TypirServices } from "../../typir.js"; -import { FunctionType, isFunctionType } from "./function-type.js"; +} from '../../services/validation.js'; +import type { TypirServices } from '../../typir.js'; +import type { FunctionType } from './function-type.js'; +import { isFunctionType } from './function-type.js'; /** * Predefined validation to produce errors for those (overloaded) functions which cannot be distinguished when calling them. * By default, only the name and the types of the input parameters are used to distinguish functions. */ export class UniqueFunctionValidation - implements ValidationRuleLifecycle +implements ValidationRuleLifecycle { protected readonly foundDeclarations: Map = new Map(); @@ -90,7 +91,7 @@ export class UniqueFunctionValidation for (const func of functions) { accept({ languageNode: func, - severity: "error", + severity: 'error', message: `Declared functions need to be unique (${key}).`, }); } diff --git a/packages/typir/src/kinds/kind.ts b/packages/typir/src/kinds/kind.ts index f7c624bd..cb70e8d5 100644 --- a/packages/typir/src/kinds/kind.ts +++ b/packages/typir/src/kinds/kind.ts @@ -30,8 +30,8 @@ export interface Kind { export function isKind(kind: unknown): kind is Kind { return ( - typeof kind === "object" && + typeof kind === 'object' && kind !== null && - typeof (kind as Kind).$name === "string" + typeof (kind as Kind).$name === 'string' ); } diff --git a/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts b/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts index 517b7852..31d66ccf 100644 --- a/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts +++ b/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts @@ -4,11 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { assertTrue } from "../../utils/utils.js"; -import { Kind, isKind } from "../kind.js"; -import { MultiplicityType } from "./multiplicity-type.js"; +import type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import { assertTrue } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { MultiplicityType } from './multiplicity-type.js'; export interface MultiplicityTypeDetails extends TypeDetails { @@ -22,14 +23,14 @@ export interface MultiplicityKindOptions { } export const MULTIPLICITY_UNLIMITED = -1; -export const MultiplicityKindName = "MultiplicityTypeKind"; +export const MultiplicityKindName = 'MultiplicityTypeKind'; /** * Types of this kind constrain a type with lower bound and upper bound, * e.g. ConstrainedType[1..*] or ConstrainedType[2..4]. */ export class MultiplicityKind implements Kind { - readonly $name: "MultiplicityTypeKind"; + readonly $name: 'MultiplicityTypeKind'; readonly services: TypirServices; readonly options: Readonly; @@ -48,7 +49,7 @@ export class MultiplicityKind implements Kind { ): MultiplicityKindOptions { return { // the default values: - symbolForUnlimited: "*", + symbolForUnlimited: '*', // the actually overriden values: ...options, }; diff --git a/packages/typir/src/kinds/multiplicity/multiplicity-type.ts b/packages/typir/src/kinds/multiplicity/multiplicity-type.ts index bc6283d7..b5d51436 100644 --- a/packages/typir/src/kinds/multiplicity/multiplicity-type.ts +++ b/packages/typir/src/kinds/multiplicity/multiplicity-type.ts @@ -4,19 +4,19 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { isSubTypeProblem } from "../../services/subtype.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import { isSubTypeProblem } from '../../services/subtype.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; import { checkValueForConflict, createKindConflict, -} from "../../utils/utils-type-comparison.js"; -import { - isMultiplicityKind, +} from '../../utils/utils-type-comparison.js'; +import type { MultiplicityKind, MultiplicityTypeDetails, -} from "./multiplicity-kind.js"; +} from './multiplicity-kind.js'; +import { isMultiplicityKind } from './multiplicity-kind.js'; export class MultiplicityType extends Type { override readonly kind: MultiplicityKind; @@ -53,14 +53,14 @@ export class MultiplicityType extends Type { ...checkValueForConflict( this.getLowerBound(), this.getLowerBound(), - "lower bound", + 'lower bound', ), ); conflicts.push( ...checkValueForConflict( this.getUpperBound(), this.getUpperBound(), - "upper bound", + 'upper bound', ), ); // check the constrained type @@ -95,7 +95,7 @@ export class MultiplicityType extends Type { ...checkValueForConflict( subType.getLowerBound(), superType.getLowerBound(), - "lower bound", + 'lower bound', this.kind.isBoundGreaterEquals, ), ); @@ -103,7 +103,7 @@ export class MultiplicityType extends Type { ...checkValueForConflict( subType.getUpperBound(), superType.getUpperBound(), - "upper bound", + 'upper bound', this.kind.isBoundGreaterEquals, ), ); diff --git a/packages/typir/src/kinds/primitive/primitive-kind.ts b/packages/typir/src/kinds/primitive/primitive-kind.ts index 4aa6055a..83a4932b 100644 --- a/packages/typir/src/kinds/primitive/primitive-kind.ts +++ b/packages/typir/src/kinds/primitive/primitive-kind.ts @@ -4,15 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { - InferCurrentTypeRule, - registerInferCurrentTypeRules, -} from "../../utils/utils-definitions.js"; -import { assertTrue } from "../../utils/utils.js"; -import { isKind, Kind } from "../kind.js"; -import { PrimitiveType } from "./primitive-type.js"; +import type { TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule } from '../../utils/utils-definitions.js'; +import { registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; +import { assertTrue } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { PrimitiveType } from './primitive-type.js'; export interface PrimitiveKindOptions { // empty for now @@ -28,7 +27,7 @@ interface CreatePrimitiveTypeDetails inferenceRules: Array>; } -export const PrimitiveKindName = "PrimitiveKind"; +export const PrimitiveKindName = 'PrimitiveKind'; export interface PrimitiveFactoryService { create( @@ -47,9 +46,9 @@ export interface PrimitiveConfigurationChain { } export class PrimitiveKind - implements Kind, PrimitiveFactoryService +implements Kind, PrimitiveFactoryService { - readonly $name: "PrimitiveKind"; + readonly $name: 'PrimitiveKind'; readonly services: TypirServices; readonly options: PrimitiveKindOptions; @@ -103,7 +102,7 @@ export function isPrimitiveKind( } class PrimitiveConfigurationChainImpl - implements PrimitiveConfigurationChain +implements PrimitiveConfigurationChain { protected readonly services: TypirServices; protected readonly kind: PrimitiveKind; diff --git a/packages/typir/src/kinds/primitive/primitive-type.ts b/packages/typir/src/kinds/primitive/primitive-type.ts index d9bc4f19..d8fab7a3 100644 --- a/packages/typir/src/kinds/primitive/primitive-type.ts +++ b/packages/typir/src/kinds/primitive/primitive-type.ts @@ -4,18 +4,15 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; import { checkValueForConflict, createKindConflict, -} from "../../utils/utils-type-comparison.js"; -import { - isPrimitiveKind, - PrimitiveKind, - PrimitiveTypeDetails, -} from "./primitive-kind.js"; +} from '../../utils/utils-type-comparison.js'; +import type { PrimitiveKind, PrimitiveTypeDetails } from './primitive-kind.js'; +import { isPrimitiveKind } from './primitive-kind.js'; export class PrimitiveType extends Type { override readonly kind: PrimitiveKind; @@ -43,7 +40,7 @@ export class PrimitiveType extends Type { return checkValueForConflict( this.getIdentifier(), otherType.getIdentifier(), - "name", + 'name', ); } else { return [ diff --git a/packages/typir/src/kinds/top/top-kind.ts b/packages/typir/src/kinds/top/top-kind.ts index 49b66625..0257a627 100644 --- a/packages/typir/src/kinds/top/top-kind.ts +++ b/packages/typir/src/kinds/top/top-kind.ts @@ -4,15 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeDetails } from "../../graph/type-node.js"; -import { TypirServices } from "../../typir.js"; -import { - InferCurrentTypeRule, - registerInferCurrentTypeRules, -} from "../../utils/utils-definitions.js"; -import { assertTrue } from "../../utils/utils.js"; -import { isKind, Kind } from "../kind.js"; -import { TopType } from "./top-type.js"; +import type { TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferCurrentTypeRule } from '../../utils/utils-definitions.js'; +import { registerInferCurrentTypeRules } from '../../utils/utils-definitions.js'; +import { assertTrue } from '../../utils/utils.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; +import { TopType } from './top-type.js'; export interface TopTypeDetails extends TypeDetails { @@ -27,7 +26,7 @@ export interface TopKindOptions { name: string; } -export const TopKindName = "TopKind"; +export const TopKindName = 'TopKind'; export interface TopFactoryService { create( @@ -44,9 +43,9 @@ export interface TopConfigurationChain { } export class TopKind - implements Kind, TopFactoryService +implements Kind, TopFactoryService { - readonly $name: "TopKind"; + readonly $name: 'TopKind'; readonly services: TypirServices; readonly options: Readonly; @@ -65,7 +64,7 @@ export class TopKind ): TopKindOptions { return { // the default values: - name: "any", + name: 'any', // the actually overriden values: ...options, }; @@ -95,7 +94,7 @@ export function isTopKind( } class TopConfigurationChainImpl - implements TopConfigurationChain +implements TopConfigurationChain { protected readonly services: TypirServices; protected readonly kind: TopKind; diff --git a/packages/typir/src/kinds/top/top-type.ts b/packages/typir/src/kinds/top/top-type.ts index 366e8bbf..49f4111f 100644 --- a/packages/typir/src/kinds/top/top-type.ts +++ b/packages/typir/src/kinds/top/top-type.ts @@ -4,12 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../../graph/type-graph.js"; -import { isType, Type } from "../../graph/type-node.js"; -import { TypeEqualityProblem } from "../../services/equality.js"; -import { TypirProblem } from "../../utils/utils-definitions.js"; -import { createKindConflict } from "../../utils/utils-type-comparison.js"; -import { isTopKind, TopKind, TopTypeDetails } from "./top-kind.js"; +import type { TypeGraphListener } from '../../graph/type-graph.js'; +import { isType, Type } from '../../graph/type-node.js'; +import { TypeEqualityProblem } from '../../services/equality.js'; +import type { TypirProblem } from '../../utils/utils-definitions.js'; +import { createKindConflict } from '../../utils/utils-type-comparison.js'; +import type { TopKind, TopTypeDetails } from './top-kind.js'; +import { isTopKind } from './top-kind.js'; export class TopType extends Type implements TypeGraphListener { override readonly kind: TopKind; diff --git a/packages/typir/src/services/assignability.ts b/packages/typir/src/services/assignability.ts index b0ad68d4..514d7593 100644 --- a/packages/typir/src/services/assignability.ts +++ b/packages/typir/src/services/assignability.ts @@ -4,27 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from "../graph/graph-algorithms.js"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { TypirProblem } from "../utils/utils-definitions.js"; -import { - ConversionEdge, - isConversionEdge, - TypeConversion, -} from "./conversion.js"; -import { TypeEquality } from "./equality.js"; -import { SubType, SubTypeEdge } from "./subtype.js"; +import type { GraphAlgorithms } from '../graph/graph-algorithms.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; +import type { TypeConversion } from './conversion.js'; +import { ConversionEdge, isConversionEdge } from './conversion.js'; +import type { TypeEquality } from './equality.js'; +import type { SubType } from './subtype.js'; +import { SubTypeEdge } from './subtype.js'; export interface AssignabilityProblem extends TypirProblem { - $problem: "AssignabilityProblem"; - $result: "AssignabilityResult"; + $problem: 'AssignabilityProblem'; + $result: 'AssignabilityResult'; source: Type; target: Type; result: false; subProblems: TypirProblem[]; } -export const AssignabilityProblem = "AssignabilityProblem"; +export const AssignabilityProblem = 'AssignabilityProblem'; export function isAssignabilityProblem( problem: unknown, ): problem is AssignabilityProblem { @@ -32,7 +30,7 @@ export function isAssignabilityProblem( } export interface AssignabilitySuccess { - $result: "AssignabilityResult"; + $result: 'AssignabilityResult'; source: Type; target: Type; result: true; @@ -45,12 +43,12 @@ export function isAssignabilitySuccess( } export type AssignabilityResult = AssignabilitySuccess | AssignabilityProblem; -export const AssignabilityResult = "AssignabilityResult"; +export const AssignabilityResult = 'AssignabilityResult'; export function isAssignabilityResult( result: unknown, ): result is AssignabilityResult { return ( - typeof result === "object" && + typeof result === 'object' && result !== null && (result as AssignabilityResult).$result === AssignabilityResult ); @@ -70,7 +68,7 @@ export interface TypeAssignability { * This implementation for assignability checks step-by-step (1) equality, (2) implicit conversion, and (3) sub-type relationships of the source and target type. */ export class DefaultTypeAssignability - implements TypeAssignability +implements TypeAssignability { protected readonly conversion: TypeConversion; protected readonly subtype: SubType; @@ -119,7 +117,7 @@ export class DefaultTypeAssignability [ConversionEdge, SubTypeEdge], (edge) => isConversionEdge(edge) - ? edge.mode === "IMPLICIT_EXPLICIT" + ? edge.mode === 'IMPLICIT_EXPLICIT' : true, ); // no explicit conversion if (path.length >= 1) { diff --git a/packages/typir/src/services/caching.ts b/packages/typir/src/services/caching.ts index 88b91b6b..e48ab3c4 100644 --- a/packages/typir/src/services/caching.ts +++ b/packages/typir/src/services/caching.ts @@ -4,11 +4,11 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeEdge } from "../graph/type-edge.js"; -import { TypeGraph } from "../graph/type-graph.js"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { assertTrue } from "../utils/utils.js"; +import type { TypeEdge } from '../graph/type-edge.js'; +import type { TypeGraph } from '../graph/type-graph.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import { assertTrue } from '../utils/utils.js'; /** * Caches relationships between types. @@ -17,12 +17,12 @@ export interface TypeRelationshipCaching { getRelationshipUnidirectional( from: Type, to: Type, - $relation: T["$relation"], + $relation: T['$relation'], ): T | undefined; getRelationshipBidirectional( from: Type, to: Type, - $relation: T["$relation"], + $relation: T['$relation'], ): T | undefined; setOrUpdateUnidirectionalRelationship( @@ -37,16 +37,16 @@ export interface TypeRelationshipCaching { export type EdgeCachingInformation = /** The analysis, whether the current relationship holds, is still ongoing. */ - | "PENDING" + | 'PENDING' /** It is unknown, whether the current relationship holds */ - | "UNKNOWN" + | 'UNKNOWN' /** The current relationship exists. */ - | "LINK_EXISTS" + | 'LINK_EXISTS' /** The current relationship does not exist. */ - | "NO_LINK"; + | 'NO_LINK'; export class DefaultTypeRelationshipCaching - implements TypeRelationshipCaching +implements TypeRelationshipCaching { protected readonly graph: TypeGraph; @@ -57,7 +57,7 @@ export class DefaultTypeRelationshipCaching getRelationshipUnidirectional( from: Type, to: Type, - $relation: T["$relation"], + $relation: T['$relation'], ): T | undefined { return from .getOutgoingEdges($relation) @@ -66,7 +66,7 @@ export class DefaultTypeRelationshipCaching getRelationshipBidirectional( from: Type, to: Type, - $relation: T["$relation"], + $relation: T['$relation'], ): T | undefined { // for bidirectional edges, check outgoing and incoming edges, since the graph contains only a single edge! return from.getEdges($relation).find((edge) => edge.to === to); @@ -93,15 +93,15 @@ export class DefaultTypeRelationshipCaching // identify the edge to store the value const edge: T | undefined = bidirectional ? this.getRelationshipBidirectional( - edgeToCache.from, - edgeToCache.to, - edgeToCache.$relation, - ) + edgeToCache.from, + edgeToCache.to, + edgeToCache.$relation, + ) : this.getRelationshipUnidirectional( - edgeToCache.from, - edgeToCache.to, - edgeToCache.$relation, - ); + edgeToCache.from, + edgeToCache.to, + edgeToCache.$relation, + ); // don't cache some values (but ensure, that PENDING is overridden/updated!) => un-set the relationship if (this.storeCachingInformation(edgeCaching) === false) { @@ -127,10 +127,10 @@ export class DefaultTypeRelationshipCaching // update data of specific edges! // Object.assign throws an error for readonly properties => it cannot be used here! const propertiesToIgnore: Array = [ - "from", - "to", - "$relation", - "cachingInformation", + 'from', + 'to', + '$relation', + 'cachingInformation', ]; const keys = Object.keys(edgeToCache) as Array; for (const v of keys) { @@ -147,7 +147,7 @@ export class DefaultTypeRelationshipCaching protected storeCachingInformation( value: EdgeCachingInformation | undefined, ): boolean { - return value === "PENDING" || value === "LINK_EXISTS"; + return value === 'PENDING' || value === 'LINK_EXISTS'; } } @@ -163,11 +163,11 @@ export interface LanguageNodeInferenceCaching { pendingGet(languageNode: unknown): boolean; } -export type CachePending = "CACHE_PENDING"; -export const CachePending = "CACHE_PENDING"; +export type CachePending = 'CACHE_PENDING'; +export const CachePending = 'CACHE_PENDING'; export class DefaultLanguageNodeInferenceCaching - implements LanguageNodeInferenceCaching +implements LanguageNodeInferenceCaching { protected cache: Map; diff --git a/packages/typir/src/services/conversion.ts b/packages/typir/src/services/conversion.ts index 11add1ef..f7291527 100644 --- a/packages/typir/src/services/conversion.ts +++ b/packages/typir/src/services/conversion.ts @@ -4,12 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from "../graph/graph-algorithms.js"; -import { isTypeEdge, TypeEdge } from "../graph/type-edge.js"; -import { TypeGraph } from "../graph/type-graph.js"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { TypeEquality } from "./equality.js"; +import type { GraphAlgorithms } from '../graph/graph-algorithms.js'; +import type { TypeEdge } from '../graph/type-edge.js'; +import { isTypeEdge } from '../graph/type-edge.js'; +import type { TypeGraph } from '../graph/type-graph.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { TypeEquality } from './equality.js'; /** * Describes the possible conversion modes. @@ -26,15 +27,15 @@ import { TypeEquality } from "./equality.js"; */ export type ConversionModeForSpecification = /** The conversion is implicitly possible. In this case, the explicit conversion is possible as well (IMPLICIT => EXPLICIT). */ - | "IMPLICIT_EXPLICIT" + | 'IMPLICIT_EXPLICIT' /** The conversion is only explicitly possible */ - | "EXPLICIT"; + | 'EXPLICIT'; export type ConversionMode = | ConversionModeForSpecification /** no conversion possible at all (this is the default mode) */ - | "NONE" + | 'NONE' /** a type is always self-convertible to itself (implicitly or explicitly), in this case no conversion is necessary */ - | "SELF"; + | 'SELF'; /** * Manages conversions between different types. @@ -139,7 +140,7 @@ export class DefaultTypeConversion implements TypeConversion { $relation: ConversionEdge, from, to, - cachingInformation: "LINK_EXISTS", + cachingInformation: 'LINK_EXISTS', mode, }; this.graph.addEdge(edge); @@ -148,7 +149,7 @@ export class DefaultTypeConversion implements TypeConversion { edge.mode = mode; } - if (mode === "IMPLICIT_EXPLICIT") { + if (mode === 'IMPLICIT_EXPLICIT') { /* check that the new edges did not introduce cycles * if it did, the from node will be reachable via a cycle path */ @@ -163,7 +164,7 @@ export class DefaultTypeConversion implements TypeConversion { protected isTransitive(mode: ConversionModeForSpecification): boolean { // by default, only IMPLICIT is transitive! - return mode === "IMPLICIT_EXPLICIT"; + return mode === 'IMPLICIT_EXPLICIT'; } getConversion(from: Type, to: Type): ConversionMode { @@ -175,25 +176,25 @@ export class DefaultTypeConversion implements TypeConversion { // special case: if both types are equal, no conversion is needed (often this check is quite fast) if (this.equality.areTypesEqual(from, to)) { - return "SELF"; + return 'SELF'; } // check whether there is a transitive relationship (in general, these checks are expensive) if ( - this.isTransitive("EXPLICIT") && - this.isTransitivelyConvertable(from, to, "EXPLICIT") + this.isTransitive('EXPLICIT') && + this.isTransitivelyConvertable(from, to, 'EXPLICIT') ) { - return "EXPLICIT"; + return 'EXPLICIT'; } if ( - this.isTransitive("IMPLICIT_EXPLICIT") && - this.isTransitivelyConvertable(from, to, "IMPLICIT_EXPLICIT") + this.isTransitive('IMPLICIT_EXPLICIT') && + this.isTransitivelyConvertable(from, to, 'IMPLICIT_EXPLICIT') ) { - return "IMPLICIT_EXPLICIT"; + return 'IMPLICIT_EXPLICIT'; } // the default case - return "NONE"; + return 'NONE'; } protected collectReachableTypes( @@ -233,24 +234,24 @@ export class DefaultTypeConversion implements TypeConversion { } isImplicitExplicitConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === "IMPLICIT_EXPLICIT"; + return this.getConversion(from, to) === 'IMPLICIT_EXPLICIT'; } isExplicitConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === "EXPLICIT"; + return this.getConversion(from, to) === 'EXPLICIT'; } isNoneConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === "NONE"; + return this.getConversion(from, to) === 'NONE'; } isSelfConvertible(from: Type, to: Type): boolean { - return this.getConversion(from, to) === "SELF"; + return this.getConversion(from, to) === 'SELF'; } isConvertible(from: Type, to: Type): boolean { const currentMode = this.getConversion(from, to); return ( - currentMode === "IMPLICIT_EXPLICIT" || - currentMode === "EXPLICIT" || - currentMode === "SELF" + currentMode === 'IMPLICIT_EXPLICIT' || + currentMode === 'EXPLICIT' || + currentMode === 'SELF' ); } @@ -272,10 +273,10 @@ export class DefaultTypeConversion implements TypeConversion { } export interface ConversionEdge extends TypeEdge { - readonly $relation: "ConversionEdge"; + readonly $relation: 'ConversionEdge'; mode: ConversionMode; } -export const ConversionEdge = "ConversionEdge"; +export const ConversionEdge = 'ConversionEdge'; export function isConversionEdge(edge: unknown): edge is ConversionEdge { return isTypeEdge(edge) && edge.$relation === ConversionEdge; diff --git a/packages/typir/src/services/equality.ts b/packages/typir/src/services/equality.ts index 54c69865..3de47105 100644 --- a/packages/typir/src/services/equality.ts +++ b/packages/typir/src/services/equality.ts @@ -4,23 +4,25 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from "langium"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { - isSpecificTypirProblem, - TypirProblem, -} from "../utils/utils-definitions.js"; -import { EdgeCachingInformation, TypeRelationshipCaching } from "./caching.js"; -import { TypeEdge, isTypeEdge } from "../graph/type-edge.js"; +import { assertUnreachable } from 'langium'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; +import { isSpecificTypirProblem } from '../utils/utils-definitions.js'; +import type { + EdgeCachingInformation, + TypeRelationshipCaching, +} from './caching.js'; +import type { TypeEdge } from '../graph/type-edge.js'; +import { isTypeEdge } from '../graph/type-edge.js'; export interface TypeEqualityProblem extends TypirProblem { - $problem: "TypeEqualityProblem"; + $problem: 'TypeEqualityProblem'; type1: Type; type2: Type; subProblems: TypirProblem[]; // might be empty } -export const TypeEqualityProblem = "TypeEqualityProblem"; +export const TypeEqualityProblem = 'TypeEqualityProblem'; export function isTypeEqualityProblem( problem: unknown, ): problem is TypeEqualityProblem { @@ -62,7 +64,7 @@ export class DefaultTypeEquality implements TypeEquality { type2, EqualityEdge, ); - const equalityCaching = linkData?.cachingInformation ?? "UNKNOWN"; + const equalityCaching = linkData?.cachingInformation ?? 'UNKNOWN'; function save( equalityCaching: EdgeCachingInformation, @@ -72,7 +74,7 @@ export class DefaultTypeEquality implements TypeEquality { $relation: EqualityEdge, from: type1, to: type2, - cachingInformation: "LINK_EXISTS", + cachingInformation: 'LINK_EXISTS', error, }; cache.setOrUpdateBidirectionalRelationship( @@ -82,7 +84,7 @@ export class DefaultTypeEquality implements TypeEquality { } // skip recursive checking - if (equalityCaching === "PENDING") { + if (equalityCaching === 'PENDING') { /** 'undefined' should be correct here ... * - since this relationship will be checked earlier/higher/upper in the call stack again * - since this values is not cached and therefore NOT reused in the earlier call! */ @@ -90,10 +92,10 @@ export class DefaultTypeEquality implements TypeEquality { } // the result is already known - if (equalityCaching === "LINK_EXISTS") { + if (equalityCaching === 'LINK_EXISTS') { return undefined; } - if (equalityCaching === "NO_LINK") { + if (equalityCaching === 'NO_LINK') { return { $problem: TypeEqualityProblem, type1, @@ -103,18 +105,18 @@ export class DefaultTypeEquality implements TypeEquality { } // do the expensive calculation now - if (equalityCaching === "UNKNOWN") { + if (equalityCaching === 'UNKNOWN') { // mark the current relationship as PENDING to detect and resolve cycling checks - save("PENDING", undefined); + save('PENDING', undefined); // do the actual calculation const result = this.calculateEquality(type1, type2); // this allows to cache results (and to re-set the PENDING state) if (result === undefined) { - save("LINK_EXISTS", undefined); + save('LINK_EXISTS', undefined); } else { - save("NO_LINK", result); + save('NO_LINK', result); } return result; } @@ -155,10 +157,10 @@ export class DefaultTypeEquality implements TypeEquality { } export interface EqualityEdge extends TypeEdge { - readonly $relation: "EqualityEdge"; + readonly $relation: 'EqualityEdge'; readonly error: TypeEqualityProblem | undefined; } -export const EqualityEdge = "EqualityEdge"; +export const EqualityEdge = 'EqualityEdge'; export function isEqualityEdge(edge: unknown): edge is EqualityEdge { return isTypeEdge(edge) && edge.$relation === EqualityEdge; diff --git a/packages/typir/src/services/inference.ts b/packages/typir/src/services/inference.ts index 838b7ba1..5440b373 100644 --- a/packages/typir/src/services/inference.ts +++ b/packages/typir/src/services/inference.ts @@ -4,30 +4,29 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from "langium"; -import { isType, Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { +import { assertUnreachable } from 'langium'; +import type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { RuleCollectorListener, RuleOptions, - RuleRegistry, -} from "../utils/rule-registration.js"; -import { - isSpecificTypirProblem, - TypirProblem, -} from "../utils/utils-definitions.js"; -import { removeFromArray, toArray } from "../utils/utils.js"; -import { LanguageNodeInferenceCaching } from "./caching.js"; +} from '../utils/rule-registration.js'; +import { RuleRegistry } from '../utils/rule-registration.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; +import { isSpecificTypirProblem } from '../utils/utils-definitions.js'; +import { removeFromArray, toArray } from '../utils/utils.js'; +import type { LanguageNodeInferenceCaching } from './caching.js'; export interface InferenceProblem extends TypirProblem { - $problem: "InferenceProblem"; + $problem: 'InferenceProblem'; languageNode: LanguageType; inferenceCandidate?: Type; location: string; rule?: TypeInferenceRule; // for debugging only, since rules have no names (so far); TODO this does not really work with TypeInferenceRuleWithoutInferringChildren subProblems: TypirProblem[]; // might be missing or empty } -export const InferenceProblem = "InferenceProblem"; +export const InferenceProblem = 'InferenceProblem'; export function isInferenceProblem( problem: unknown, ): problem is InferenceProblem { @@ -35,8 +34,8 @@ export function isInferenceProblem( } // Type and Value to indicate, that an inference rule is intended for another case, and therefore is unable to infer a type for the current case. -export type InferenceRuleNotApplicable = "N/A"; // or 'undefined' instead? -export const InferenceRuleNotApplicable = "N/A"; // or 'undefined' instead? +export type InferenceRuleNotApplicable = 'N/A'; // or 'undefined' instead? +export const InferenceRuleNotApplicable = 'N/A'; // or 'undefined' instead? export type TypeInferenceResultWithoutInferringChildren = /** the identified type */ @@ -177,7 +176,7 @@ export interface TypeInferenceCollector { } export class DefaultTypeInferenceCollector - implements +implements TypeInferenceCollector, RuleCollectorListener> { @@ -273,7 +272,7 @@ export class DefaultTypeInferenceCollector protected checkForError(languageNode: LanguageType): void { if (languageNode === undefined || languageNode === null) { - throw new Error("Language node must be not undefined/null!"); + throw new Error('Language node must be not undefined/null!'); } } @@ -326,7 +325,7 @@ export class DefaultTypeInferenceCollector collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: "found no applicable inference rules", + location: 'found no applicable inference rules', subProblems: [], }); } @@ -338,7 +337,7 @@ export class DefaultTypeInferenceCollector languageNode: LanguageType, collectedInferenceProblems: Array>, ): Type | undefined { - if (typeof rule === "function") { + if (typeof rule === 'function') { // simple case without type inference for children const ruleResult: TypeInferenceResultWithoutInferringChildren = rule(languageNode, this.services); @@ -346,7 +345,7 @@ export class DefaultTypeInferenceCollector ruleResult, collectedInferenceProblems, ); - } else if (typeof rule === "object") { + } else if (typeof rule === 'object') { // more complex case with inferring the type for children const ruleResult: TypeInferenceResultWithInferringChildren = rule.inferTypeWithoutChildren(languageNode, this.services); @@ -378,7 +377,7 @@ export class DefaultTypeInferenceCollector collectedInferenceProblems.push({ $problem: InferenceProblem, languageNode: languageNode, - location: "inferring depending children", + location: 'inferring depending children', rule, subProblems: childTypeProblems, }); @@ -546,7 +545,7 @@ export class CompositeTypeInferenceRule return >{ $problem: InferenceProblem, languageNode: languageNode, - location: "sub-rules for inference", + location: 'sub-rules for inference', rule: this, subProblems: result, }; @@ -559,7 +558,7 @@ export class CompositeTypeInferenceRule _childrenTypes: Array, _typir: TypirServices, ): Type | InferenceProblem { - throw new Error("This function will not be called."); + throw new Error('This function will not be called.'); } override onAddedRule( diff --git a/packages/typir/src/services/kind-registry.ts b/packages/typir/src/services/kind-registry.ts index e4a1a2f0..eb6e1151 100644 --- a/packages/typir/src/services/kind-registry.ts +++ b/packages/typir/src/services/kind-registry.ts @@ -4,20 +4,20 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Kind } from "../kinds/kind.js"; -import { TypirServices } from "../typir.js"; +import type { Kind } from '../kinds/kind.js'; +import type { TypirServices } from '../typir.js'; export interface KindRegistry { register(kind: Kind): void; - get(type: T["$name"]): T | undefined; + get(type: T['$name']): T | undefined; getOrCreateKind( - type: T["$name"], + type: T['$name'], factory: (services: TypirServices) => T, ): T; } export class DefaultKindRegistry - implements KindRegistry +implements KindRegistry { protected readonly services: TypirServices; protected readonly kinds: Map = new Map(); // name of kind => kind (for an easier look-up) @@ -39,12 +39,12 @@ export class DefaultKindRegistry } } - get(type: T["$name"]): T | undefined { + get(type: T['$name']): T | undefined { return this.kinds.get(type) as T | undefined; } getOrCreateKind( - type: T["$name"], + type: T['$name'], factory: (services: TypirServices) => T, ): T { const existing = this.get(type); diff --git a/packages/typir/src/services/language.ts b/packages/typir/src/services/language.ts index 70578ccc..ee08166c 100644 --- a/packages/typir/src/services/language.ts +++ b/packages/typir/src/services/language.ts @@ -44,7 +44,7 @@ export interface LanguageService { * This default implementation provides no information about the current language. */ export class DefaultLanguageService - implements LanguageService +implements LanguageService { getLanguageNodeKey(_languageNode: LanguageType): string | undefined { return undefined; diff --git a/packages/typir/src/services/operator.ts b/packages/typir/src/services/operator.ts index c501a848..cd7738c5 100644 --- a/packages/typir/src/services/operator.ts +++ b/packages/typir/src/services/operator.ts @@ -4,17 +4,15 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../graph/type-node.js"; -import { TypeInitializer } from "../initialization/type-initializer.js"; -import { - FunctionFactoryService, - NO_PARAMETER_NAME, -} from "../kinds/function/function-kind.js"; -import { FunctionType } from "../kinds/function/function-type.js"; -import { TypirServices } from "../typir.js"; -import { NameTypePair } from "../utils/utils-definitions.js"; -import { toArray } from "../utils/utils.js"; -import { ValidationProblemAcceptor } from "./validation.js"; +import type { Type } from '../graph/type-node.js'; +import type { TypeInitializer } from '../initialization/type-initializer.js'; +import type { FunctionFactoryService } from '../kinds/function/function-kind.js'; +import { NO_PARAMETER_NAME } from '../kinds/function/function-kind.js'; +import type { FunctionType } from '../kinds/function/function-type.js'; +import type { TypirServices } from '../typir.js'; +import type { NameTypePair } from '../utils/utils-definitions.js'; +import { toArray } from '../utils/utils.js'; +import type { ValidationProblemAcceptor } from './validation.js'; export interface InferOperatorWithSingleOperand< LanguageType, @@ -182,7 +180,7 @@ export interface OperatorConfigurationGenericChain { * All operands are mandatory. */ export class DefaultOperatorFactory - implements OperatorFactoryService +implements OperatorFactoryService { protected readonly services: TypirServices; @@ -228,7 +226,7 @@ export class DefaultOperatorFactory } class OperatorConfigurationUnaryChainImpl - implements OperatorConfigurationUnaryChain +implements OperatorConfigurationUnaryChain { protected readonly services: TypirServices; protected readonly typeDetails: CreateUnaryOperatorDetails; @@ -263,7 +261,7 @@ class OperatorConfigurationUnaryChainImpl name: this.typeDetails.name, outputType: signature.return, inputParameter: [ - { name: "operand", type: signature.operand }, + { name: 'operand', type: signature.operand }, ], }, ); @@ -278,7 +276,7 @@ class OperatorConfigurationUnaryChainImpl } class OperatorConfigurationBinaryChainImpl - implements OperatorConfigurationBinaryChain +implements OperatorConfigurationBinaryChain { protected readonly services: TypirServices; protected readonly typeDetails: CreateBinaryOperatorDetails; @@ -313,8 +311,8 @@ class OperatorConfigurationBinaryChainImpl name: this.typeDetails.name, outputType: signature.return, inputParameter: [ - { name: "left", type: signature.left }, - { name: "right", type: signature.right }, + { name: 'left', type: signature.left }, + { name: 'right', type: signature.right }, ], }, ); @@ -329,7 +327,7 @@ class OperatorConfigurationBinaryChainImpl } class OperatorConfigurationTernaryChainImpl - implements OperatorConfigurationTernaryChain +implements OperatorConfigurationTernaryChain { protected readonly services: TypirServices; protected readonly typeDetails: CreateTernaryOperatorDetails; @@ -364,9 +362,9 @@ class OperatorConfigurationTernaryChainImpl name: this.typeDetails.name, outputType: signature.return, inputParameter: [ - { name: "first", type: signature.first }, - { name: "second", type: signature.second }, - { name: "third", type: signature.third }, + { name: 'first', type: signature.first }, + { name: 'second', type: signature.second }, + { name: 'third', type: signature.third }, ], }, ); @@ -381,7 +379,7 @@ class OperatorConfigurationTernaryChainImpl } class OperatorConfigurationGenericChainImpl - implements OperatorConfigurationGenericChain +implements OperatorConfigurationGenericChain { protected readonly services: TypirServices; protected readonly typeDetails: CreateGenericOperatorDetails; @@ -430,12 +428,12 @@ class OperatorConfigurationGenericChainImpl languageKey: inferenceRule.languageKey, filter: inferenceRule.filter ? ( - languageNode: LanguageType, - ): languageNode is LanguageType => - inferenceRule.filter!( - languageNode, - this.typeDetails.name, - ) + languageNode: LanguageType, + ): languageNode is LanguageType => + inferenceRule.filter!( + languageNode, + this.typeDetails.name, + ) : undefined, matching: (languageNode: LanguageType) => inferenceRule.matching(languageNode, this.typeDetails.name), @@ -475,15 +473,15 @@ class OperatorConfigurationGenericChainImpl | InferOperatorWithMultipleOperands, languageNode: LanguageType, ): LanguageType[] { - return "operands" in inferenceRule + return 'operands' in inferenceRule ? ( - inferenceRule as InferOperatorWithMultipleOperands - ).operands(languageNode, this.typeDetails.name) + inferenceRule as InferOperatorWithMultipleOperands + ).operands(languageNode, this.typeDetails.name) : [ - ( - inferenceRule as InferOperatorWithSingleOperand - ).operand(languageNode, this.typeDetails.name), - ]; + ( + inferenceRule as InferOperatorWithSingleOperand + ).operand(languageNode, this.typeDetails.name), + ]; } protected getFunctionFactory(): FunctionFactoryService { @@ -497,7 +495,7 @@ function toSignatureArray(values: { signature?: T; signatures?: T[] }): T[] { result.push(values.signature); } if (result.length <= 0) { - throw new Error("At least one signature must be given!"); + throw new Error('At least one signature must be given!'); } return result; } diff --git a/packages/typir/src/services/printing.ts b/packages/typir/src/services/printing.ts index cd2ff29e..bdcfde6f 100644 --- a/packages/typir/src/services/printing.ts +++ b/packages/typir/src/services/printing.ts @@ -4,23 +4,27 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../graph/type-node.js"; -import { TypirProblem } from "../utils/utils-definitions.js"; -import { +import type { Type } from '../graph/type-node.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; +import type { IndexedTypeConflict, ValueConflict, +} from '../utils/utils-type-comparison.js'; +import { isIndexedTypeConflict, isValueConflict, -} from "../utils/utils-type-comparison.js"; -import { toArray } from "../utils/utils.js"; -import { - AssignabilityProblem, - isAssignabilityProblem, -} from "./assignability.js"; -import { TypeEqualityProblem, isTypeEqualityProblem } from "./equality.js"; -import { InferenceProblem, isInferenceProblem } from "./inference.js"; -import { SubTypeProblem, isSubTypeProblem } from "./subtype.js"; -import { ValidationProblem, isValidationProblem } from "./validation.js"; +} from '../utils/utils-type-comparison.js'; +import { toArray } from '../utils/utils.js'; +import type { AssignabilityProblem } from './assignability.js'; +import { isAssignabilityProblem } from './assignability.js'; +import type { TypeEqualityProblem } from './equality.js'; +import { isTypeEqualityProblem } from './equality.js'; +import type { InferenceProblem } from './inference.js'; +import { isInferenceProblem } from './inference.js'; +import type { SubTypeProblem } from './subtype.js'; +import { isSubTypeProblem } from './subtype.js'; +import type { ValidationProblem } from './validation.js'; +import { isValidationProblem } from './validation.js'; export interface ProblemPrinter { printValueConflict(problem: ValueConflict): string; @@ -57,7 +61,7 @@ export interface ProblemPrinter { } export class DefaultTypeConflictPrinter - implements ProblemPrinter +implements ProblemPrinter { constructor() {} @@ -84,7 +88,7 @@ export class DefaultTypeConflictPrinter ): string { const left = problem.expected; const right = problem.actual; - let result = ""; + let result = ''; if (problem.propertyName) { if (problem.propertyIndex) { result += `For property '${problem.propertyName} at index ${problem.propertyIndex}', `; @@ -94,7 +98,7 @@ export class DefaultTypeConflictPrinter } else if (problem.propertyIndex) { result += `At index ${problem.propertyIndex}, `; } else { - result += "At an unknown location, "; + result += 'At an unknown location, '; } if (left !== undefined && right !== undefined) { result += `the types '${this.printTypeName(left)}' and '${this.printTypeName(right)}' do not match.`; @@ -103,7 +107,7 @@ export class DefaultTypeConflictPrinter } else if (left === undefined && right !== undefined) { result += `there is no type on the left to match with the type '${this.printTypeName(right)}' on the right.`; } else { - result += "both types are unclear."; + result += 'both types are unclear.'; } result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); @@ -145,7 +149,7 @@ export class DefaultTypeConflictPrinter if (problem.inferenceCandidate) { result += ` of the type '${this.printTypeName(problem.inferenceCandidate)}' as candidate to infer`; } - result += ", some problems occurred."; + result += ', some problems occurred.'; // Since Rules have no name, it is not possible to print problem.rule here. result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); @@ -184,14 +188,14 @@ export class DefaultTypeConflictPrinter } printTypirProblems(problems: TypirProblem[], level: number = 0): string { - return problems.map((p) => this.printTypirProblem(p, level)).join("\n"); + return problems.map((p) => this.printTypirProblem(p, level)).join('\n'); } printLanguageNode( languageNode: LanguageType, sentenceBegin: boolean = false, ): string { - return `${sentenceBegin ? "T" : "t"}he language node '${languageNode}'`; + return `${sentenceBegin ? 'T' : 't'}he language node '${languageNode}'`; } printTypeName(type: Type): string { @@ -209,7 +213,7 @@ export class DefaultTypeConflictPrinter ): string { const problems = toArray(subProblems); if (problems.length >= 1) { - return result + "\n" + this.printTypirProblems(problems, level + 1); + return result + '\n' + this.printTypirProblems(problems, level + 1); } else { return result; } diff --git a/packages/typir/src/services/subtype.ts b/packages/typir/src/services/subtype.ts index ca7e5214..1c2c9641 100644 --- a/packages/typir/src/services/subtype.ts +++ b/packages/typir/src/services/subtype.ts @@ -4,28 +4,29 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { GraphAlgorithms } from "../graph/graph-algorithms.js"; -import { isTypeEdge, TypeEdge } from "../graph/type-edge.js"; -import { TypeGraph } from "../graph/type-graph.js"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { TypirProblem } from "../utils/utils-definitions.js"; +import type { GraphAlgorithms } from '../graph/graph-algorithms.js'; +import type { TypeEdge } from '../graph/type-edge.js'; +import { isTypeEdge } from '../graph/type-edge.js'; +import type { TypeGraph } from '../graph/type-graph.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; export interface SubTypeProblem extends TypirProblem { - $problem: "SubTypeProblem"; - $result: "SubTypeResult"; + $problem: 'SubTypeProblem'; + $result: 'SubTypeResult'; superType: Type; subType: Type; result: false; subProblems: TypirProblem[]; // might be empty } -export const SubTypeProblem = "SubTypeProblem"; +export const SubTypeProblem = 'SubTypeProblem'; export function isSubTypeProblem(problem: unknown): problem is SubTypeProblem { return isSubTypeResult(problem) && problem.result === false; } export interface SubTypeSuccess { - $result: "SubTypeResult"; + $result: 'SubTypeResult'; superType: Type; subType: Type; result: true; @@ -36,10 +37,10 @@ export function isSubTypeSuccess(success: unknown): success is SubTypeSuccess { } export type SubTypeResult = SubTypeSuccess | SubTypeProblem; -export const SubTypeResult = "SubTypeResult"; +export const SubTypeResult = 'SubTypeResult'; export function isSubTypeResult(result: unknown): result is SubTypeResult { return ( - typeof result === "object" && + typeof result === 'object' && result !== null && (result as SubTypeResult).$result === SubTypeResult ); @@ -156,12 +157,12 @@ export class DefaultSubType implements SubType { $relation: SubTypeEdge, from: subType, to: superType, - cachingInformation: "LINK_EXISTS", + cachingInformation: 'LINK_EXISTS', error: undefined, }; this.graph.addEdge(edge); } else { - edge.cachingInformation = "LINK_EXISTS"; + edge.cachingInformation = 'LINK_EXISTS'; } // check for cycles @@ -181,10 +182,10 @@ export class DefaultSubType implements SubType { } export interface SubTypeEdge extends TypeEdge { - readonly $relation: "SubTypeEdge"; + readonly $relation: 'SubTypeEdge'; readonly error: SubTypeProblem | undefined; } -export const SubTypeEdge = "SubTypeEdge"; +export const SubTypeEdge = 'SubTypeEdge'; export function isSubTypeEdge(edge: unknown): edge is SubTypeEdge { return isTypeEdge(edge) && edge.$relation === SubTypeEdge; diff --git a/packages/typir/src/services/validation.ts b/packages/typir/src/services/validation.ts index c3dffeab..e556aa13 100644 --- a/packages/typir/src/services/validation.ts +++ b/packages/typir/src/services/validation.ts @@ -4,26 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, isType } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { +import type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { RuleCollectorListener, RuleOptions, - RuleRegistry, -} from "../utils/rule-registration.js"; -import { - TypirProblem, - isSpecificTypirProblem, -} from "../utils/utils-definitions.js"; -import { - TypeCheckStrategy, - createTypeCheckStrategy, -} from "../utils/utils-type-comparison.js"; -import { removeFromArray, toArray } from "../utils/utils.js"; -import { TypeInferenceCollector } from "./inference.js"; -import { ProblemPrinter } from "./printing.js"; - -export type Severity = "error" | "warning" | "info" | "hint"; +} from '../utils/rule-registration.js'; +import { RuleRegistry } from '../utils/rule-registration.js'; +import type { TypirProblem } from '../utils/utils-definitions.js'; +import { isSpecificTypirProblem } from '../utils/utils-definitions.js'; +import type { TypeCheckStrategy } from '../utils/utils-type-comparison.js'; +import { createTypeCheckStrategy } from '../utils/utils-type-comparison.js'; +import { removeFromArray, toArray } from '../utils/utils.js'; +import type { TypeInferenceCollector } from './inference.js'; +import type { ProblemPrinter } from './printing.js'; + +export type Severity = 'error' | 'warning' | 'info' | 'hint'; export interface ValidationMessageDetails< LanguageType, @@ -40,11 +37,11 @@ export interface ValidationProblem< LanguageType, T extends LanguageType = LanguageType, > extends ValidationMessageDetails, - TypirProblem { - $problem: "ValidationProblem"; + TypirProblem { + $problem: 'ValidationProblem'; subProblems?: TypirProblem[]; } -export const ValidationProblem = "ValidationProblem"; +export const ValidationProblem = 'ValidationProblem'; export function isValidationProblem< LanguageType, T extends LanguageType = LanguageType, @@ -56,7 +53,7 @@ export function isValidationProblem< export type ReducedValidationProblem< LanguageType, T extends LanguageType = LanguageType, -> = Omit, "$problem">; +> = Omit, '$problem'>; export type ValidationProblemAcceptor = < T extends LanguageType = LanguageType, @@ -169,7 +166,7 @@ export interface ValidationConstraints { } export class DefaultValidationConstraints - implements ValidationConstraints +implements ValidationConstraints { protected readonly services: TypirServices; protected readonly inference: TypeInferenceCollector; @@ -194,7 +191,7 @@ export class DefaultValidationConstraints this.ensureNodeRelatedWithType( sourceNode, expected, - "ASSIGNABLE_TYPE", + 'ASSIGNABLE_TYPE', false, accept, message, @@ -214,7 +211,7 @@ export class DefaultValidationConstraints this.ensureNodeRelatedWithType( sourceNode, expected, - "EQUAL_TYPE", + 'EQUAL_TYPE', false, accept, message, @@ -234,7 +231,7 @@ export class DefaultValidationConstraints this.ensureNodeRelatedWithType( sourceNode, notExpected, - "EQUAL_TYPE", + 'EQUAL_TYPE', true, accept, message, @@ -281,10 +278,10 @@ export class DefaultValidationConstraints languageNode: details.languageNode ?? languageNode, languageProperty: details.languageProperty, languageIndex: details.languageIndex, - severity: details.severity ?? "error", + severity: details.severity ?? 'error', message: details.message ?? - `'${actualType.getIdentifier()}' is ${negated ? "" : "not "}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, + `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, subProblems: [comparisonResult], }); } @@ -298,10 +295,10 @@ export class DefaultValidationConstraints languageNode: details.languageNode ?? languageNode, languageProperty: details.languageProperty, languageIndex: details.languageIndex, - severity: details.severity ?? "error", + severity: details.severity ?? 'error', message: details.message ?? - `'${actualType.getIdentifier()}' is ${negated ? "" : "not "}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, + `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, subProblems: [], // no sub-problems are available! }); } else { @@ -373,7 +370,7 @@ export interface ValidationCollector { } export class DefaultValidationCollector - implements +implements ValidationCollector, RuleCollectorListener> { @@ -508,7 +505,7 @@ export class DefaultValidationCollector rule: ValidationRule, givenOptions?: Partial, ): void { - if (typeof rule === "function") { + if (typeof rule === 'function') { this.ruleRegistryFunctional.addRule( rule as ValidationRuleFunctional, givenOptions, @@ -525,7 +522,7 @@ export class DefaultValidationCollector rule: ValidationRule, givenOptions?: Partial, ): void { - if (typeof rule === "function") { + if (typeof rule === 'function') { this.ruleRegistryFunctional.removeRule( rule as ValidationRuleFunctional, givenOptions, diff --git a/packages/typir/src/test/predefined-language-nodes.ts b/packages/typir/src/test/predefined-language-nodes.ts index 21dea93c..6d08baf9 100644 --- a/packages/typir/src/test/predefined-language-nodes.ts +++ b/packages/typir/src/test/predefined-language-nodes.ts @@ -4,11 +4,9 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { DefaultLanguageService } from "../services/language.js"; -import { InferOperatorWithMultipleOperands } from "../services/operator.js"; -import { DefaultTypeConflictPrinter } from "../services/printing.js"; - -/* eslint-disable @typescript-eslint/parameter-properties */ +import { DefaultLanguageService } from '../services/language.js'; +import type { InferOperatorWithMultipleOperands } from '../services/operator.js'; +import { DefaultTypeConflictPrinter } from '../services/printing.js'; /** * Base class for all language nodes, @@ -24,7 +22,7 @@ export abstract class TestLanguageNode { const obj = this; const properties = Object.entries(obj) .map((key, value) => `${key}: ${this.printObject(value)}`) - .join(", "); + .join(', '); return `${this.constructor.name}(${properties})`; } @@ -32,7 +30,7 @@ export abstract class TestLanguageNode { if (Array.isArray(obj)) { const entries = Array.from(obj.values()) .map((v) => this.printObject(v)) - .join(", "); + .join(', '); return `[${entries}]`; } if (obj instanceof TestLanguageNode) { @@ -81,12 +79,12 @@ export const double3_0 = new DoubleLiteral(3.0); export const booleanTrue = new BooleanLiteral(true); export const booleanFalse = new BooleanLiteral(false); -export const string123 = new StringLiteral("123"); -export const string456 = new StringLiteral("456"); -export const string2 = new StringLiteral("2"); -export const string3 = new StringLiteral("3"); -export const stringHello = new StringLiteral("Hello"); -export const stringWorld = new StringLiteral("World"); +export const string123 = new StringLiteral('123'); +export const string456 = new StringLiteral('456'); +export const string2 = new StringLiteral('2'); +export const string3 = new StringLiteral('3'); +export const stringHello = new StringLiteral('Hello'); +export const stringWorld = new StringLiteral('World'); export class ClassConstructorCall extends TestExpressionNode { constructor(public className: string) { @@ -157,7 +155,7 @@ export class TestProblemPrinter extends DefaultTypeConflictPrinter(): Module< services.infrastructure.Kinds.getOrCreateKind( ClassKindName, (services) => - new ClassKind(services, { typing: "Nominal" }), + new ClassKind(services, { typing: 'Nominal' }), ), Top: (services) => services.infrastructure.Kinds.getOrCreateKind( @@ -225,8 +208,8 @@ export function createTypirServices( export type DeepPartial = T[keyof T] extends Function ? T : { - [P in keyof T]?: DeepPartial; - }; + [P in keyof T]?: DeepPartial; + }; /** * Language-specific services to be partially overridden via dependency injection. diff --git a/packages/typir/src/utils/dependency-injection.ts b/packages/typir/src/utils/dependency-injection.ts index 77789e49..e398038a 100644 --- a/packages/typir/src/utils/dependency-injection.ts +++ b/packages/typir/src/utils/dependency-injection.ts @@ -84,7 +84,7 @@ export function inject< return _inject(module); } -const isProxy = Symbol("isProxy"); +const isProxy = Symbol('isProxy'); /** * Eagerly load all services in the given dependency injection container. This is sometimes @@ -108,7 +108,7 @@ function _inject(module: Module, injector?: any): T { deleteProperty: () => false, set: () => { throw new Error( - "Cannot set property on injected service container", + 'Cannot set property on injected service container', ); }, get: (obj, prop) => { @@ -155,7 +155,7 @@ function _resolve( if (prop in obj) { if (obj[prop] instanceof Error) { throw new Error( - "Construction failure. Please make sure that your dependencies are constructable.", + 'Construction failure. Please make sure that your dependencies are constructable.', { cause: obj[prop] }, ); } @@ -173,7 +173,7 @@ function _resolve( obj[prop] = __requested__; try { obj[prop] = - typeof value === "function" + typeof value === 'function' ? value(injector) : _inject(value, injector); } catch (error) { @@ -201,8 +201,8 @@ function _merge(target: Module, source?: Module): Module { if ( value1 !== null && value2 !== null && - typeof value1 === "object" && - typeof value2 === "object" + typeof value1 === 'object' && + typeof value2 === 'object' ) { target[key] = _merge(value1, value2); } else { diff --git a/packages/typir/src/utils/rule-registration.ts b/packages/typir/src/utils/rule-registration.ts index 46ba3342..b78093a8 100644 --- a/packages/typir/src/utils/rule-registration.ts +++ b/packages/typir/src/utils/rule-registration.ts @@ -4,10 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from "../graph/type-graph.js"; -import { Type } from "../graph/type-node.js"; -import { TypirServices } from "../typir.js"; -import { removeFromArray, toArray, toArrayWithValue } from "./utils.js"; +import type { TypeGraphListener } from '../graph/type-graph.js'; +import type { Type } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import { removeFromArray, toArray, toArrayWithValue } from './utils.js'; export interface RuleOptions { /** @@ -365,7 +365,7 @@ export class RuleRegistry implements TypeGraphListener { } protected getBoundToTypeKey(boundToType?: Type): string { - return boundToType?.getIdentifier() ?? ""; + return boundToType?.getIdentifier() ?? ''; } /* Get informed about deleted types in order to remove rules which are bound to them. */ @@ -408,7 +408,7 @@ export class RuleRegistry implements TypeGraphListener { ); } } else { - throw new Error("Removed type does not exist here"); + throw new Error('Removed type does not exist here'); } } } diff --git a/packages/typir/src/utils/test-utils.ts b/packages/typir/src/utils/test-utils.ts index a4121351..93fe7446 100644 --- a/packages/typir/src/utils/test-utils.ts +++ b/packages/typir/src/utils/test-utils.ts @@ -4,21 +4,20 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { expect } from "vitest"; -import { Type } from "../graph/type-node.js"; +import { expect } from 'vitest'; +import type { Type } from '../graph/type-node.js'; +import type { TestLanguageNode } from '../test/predefined-language-nodes.js'; import { - TestLanguageNode, TestLanguageService, TestProblemPrinter, -} from "../test/predefined-language-nodes.js"; +} from '../test/predefined-language-nodes.js'; +import type { PartialTypirServices, TypirServices } from '../typir.js'; import { createDefaultTypirServicesModule, createTypirServices, - PartialTypirServices, - TypirServices, -} from "../typir.js"; -import { Module } from "./dependency-injection.js"; -import { Severity } from "../services/validation.js"; +} from '../typir.js'; +import type { Module } from './dependency-injection.js'; +import type { Severity } from '../services/validation.js'; /** * Testing utility to check, that exactly the expected types are in the type system. @@ -39,10 +38,10 @@ export function expectTypirTypes( filterTypes, ); types.forEach((type) => - expect(type.getInitializationState()).toBe("Completed"), + expect(type.getInitializationState()).toBe('Completed'), ); // check that all types are 'Completed' const typeNames = types.map((t) => t.getName()); - expect(typeNames, typeNames.join(", ")).toHaveLength( + expect(typeNames, typeNames.join(', ')).toHaveLength( namesOfExpectedTypes.length, ); for (const name of namesOfExpectedTypes) { @@ -52,7 +51,7 @@ export function expectTypirTypes( } expect( typeNames, - `There are more types than expected: ${typeNames.join(", ")}`, + `There are more types than expected: ${typeNames.join(', ')}`, ).toHaveLength(0); return types; } @@ -288,8 +287,8 @@ function compareValidationIssuesLogic( } } // report the result - const msgExpected = expectedErrors.join("\n").trim(); - const msgActual = actualIssues.join("\n").trim(); + const msgExpected = expectedErrors.join('\n').trim(); + const msgActual = actualIssues.join('\n').trim(); if (msgExpected.length >= 1 && msgActual.length >= 1) { if (options?.strict) { expect.fail( @@ -343,8 +342,8 @@ function compareValidationIssuesLogicAbsent( } } // report the result - const msgForbidden = forbiddenErrors.join("\n").trim(); - const msgActual = actualIssues.join("\n").trim(); + const msgForbidden = forbiddenErrors.join('\n').trim(); + const msgActual = actualIssues.join('\n').trim(); if (msgForbidden.length >= 1 && msgActual.length >= 1) { expect.fail( `Found these forbidden issues:\n${msgForbidden}\nThese other issues are ignored:\n${msgActual}`, diff --git a/packages/typir/src/utils/utils-definitions.ts b/packages/typir/src/utils/utils-definitions.ts index 029df746..d333d99f 100644 --- a/packages/typir/src/utils/utils-definitions.ts +++ b/packages/typir/src/utils/utils-definitions.ts @@ -4,20 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type } from "../graph/type-node.js"; -import { TypeInitializer } from "../initialization/type-initializer.js"; -import { - InferenceRuleNotApplicable, +import type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypeInitializer } from '../initialization/type-initializer.js'; +import type { TypeInferenceRule, TypeInferenceRuleOptions, -} from "../services/inference.js"; -import { +} from '../services/inference.js'; +import { InferenceRuleNotApplicable } from '../services/inference.js'; +import type { ValidationProblemAcceptor, ValidationRule, ValidationRuleOptions, -} from "../services/validation.js"; -import { TypirServices } from "../typir.js"; -import { toArray } from "./utils.js"; +} from '../services/validation.js'; +import type { TypirServices } from '../typir.js'; +import { toArray } from './utils.js'; /** * Common interface of all problems/errors/messages which should be shown to users of DSLs which are type-checked with Typir. @@ -31,7 +32,7 @@ export function isSpecificTypirProblem( $problem: string, ): problem is TypirProblem { return ( - typeof problem === "object" && + typeof problem === 'object' && problem !== null && (problem as TypirProblem).$problem === $problem ); @@ -49,9 +50,9 @@ export type NameTypePair = { }; export function isNameTypePair(type: unknown): type is NameTypePair { return ( - typeof type === "object" && + typeof type === 'object' && type !== null && - typeof (type as NameTypePair).name === "string" && + typeof (type as NameTypePair).name === 'string' && isType((type as NameTypePair).type) ); } @@ -121,7 +122,7 @@ export interface RegistrationOptions { * 'MYSELF' indicates, that the caller is responsible to register the validation rule, * otherwise the given options are used to register the return validation rule now. */ - registration: "MYSELF" | Partial; + registration: 'MYSELF' | Partial; } // diff --git a/packages/typir/src/utils/utils-type-comparison.ts b/packages/typir/src/utils/utils-type-comparison.ts index 2300b7d1..81987e6f 100644 --- a/packages/typir/src/utils/utils-type-comparison.ts +++ b/packages/typir/src/utils/utils-type-comparison.ts @@ -4,36 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertUnreachable } from "langium"; -import { isType, Type } from "../graph/type-node.js"; -import { Kind } from "../kinds/kind.js"; -import { InferenceProblem } from "../services/inference.js"; -import { TypirServices } from "../typir.js"; -import { assertTrue } from "../utils/utils.js"; -import { - isNameTypePair, - isSpecificTypirProblem, - NameTypePair, - TypirProblem, -} from "./utils-definitions.js"; +import { assertUnreachable } from 'langium'; +import type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { Kind } from '../kinds/kind.js'; +import type { InferenceProblem } from '../services/inference.js'; +import type { TypirServices } from '../typir.js'; +import { assertTrue } from '../utils/utils.js'; +import type { NameTypePair, TypirProblem } from './utils-definitions.js'; +import { isNameTypePair, isSpecificTypirProblem } from './utils-definitions.js'; export type TypeCheckStrategy = - | "EQUAL_TYPE" // the most strict checking - | "ASSIGNABLE_TYPE" // SUB_TYPE or implicit conversion - | "SUB_TYPE"; // more relaxed checking + | 'EQUAL_TYPE' // the most strict checking + | 'ASSIGNABLE_TYPE' // SUB_TYPE or implicit conversion + | 'SUB_TYPE'; // more relaxed checking export function createTypeCheckStrategy( strategy: TypeCheckStrategy, typir: TypirServices, ): (t1: Type, t2: Type) => TypirProblem | undefined { switch (strategy) { - case "ASSIGNABLE_TYPE": + case 'ASSIGNABLE_TYPE': return typir.Assignability.getAssignabilityProblem // t1 === source, t2 === target .bind(typir.Assignability); - case "EQUAL_TYPE": + case 'EQUAL_TYPE': return typir.Equality.getTypeEqualityProblem // (unordered, order does not matter) .bind(typir.Equality); - case "SUB_TYPE": + case 'SUB_TYPE': return typir.Subtype.getSubTypeProblem // t1 === sub, t2 === super .bind(typir.Subtype); // .bind(...) is required to have the correct value for 'this' inside the referenced function/method! @@ -44,13 +41,13 @@ export function createTypeCheckStrategy( } export interface ValueConflict extends TypirProblem { - readonly $problem: "ValueConflict"; + readonly $problem: 'ValueConflict'; // 'undefined' means value is missing, 'string' is the string representation of the value firstValue: string | undefined; secondValue: string | undefined; location: string; } -export const ValueConflict = "ValueConflict"; +export const ValueConflict = 'ValueConflict'; export function isValueConflict(problem: unknown): problem is ValueConflict { return isSpecificTypirProblem(problem, ValueConflict); } @@ -87,12 +84,12 @@ export function createKindConflict( $problem: ValueConflict, firstValue: first.$name, secondValue: second.$name, - location: "kind", + location: 'kind', }; } export interface IndexedTypeConflict extends TypirProblem { - $problem: "IndexedTypeConflict"; + $problem: 'IndexedTypeConflict'; // 'undefined' means type or information is missing, 'string' is for data which are no Types expected: Type | undefined; // first, left actual: Type | undefined; // second, right @@ -101,7 +98,7 @@ export interface IndexedTypeConflict extends TypirProblem { propertyName?: string; subProblems: TypirProblem[]; } -export const IndexedTypeConflict = "IndexedTypeConflict"; +export const IndexedTypeConflict = 'IndexedTypeConflict'; export function isIndexedTypeConflict( problem: unknown, ): problem is IndexedTypeConflict { @@ -165,7 +162,7 @@ export function checkTypes( const subProblems: TypirProblem[] = []; if (isLeftPair && isRightPair && checkNamesOfNameTypePairs) { subProblems.push( - ...checkValueForConflict(left.name, right.name, "name"), + ...checkValueForConflict(left.name, right.name, 'name'), ); } const relationCheckResult = relationToCheck(leftType, rightType); @@ -181,8 +178,8 @@ export function checkTypes( propertyName: isLeftPair ? left.name : isRightPair - ? right.name - : undefined, + ? right.name + : undefined, subProblems: subProblems, }); } else { @@ -351,7 +348,7 @@ export function checkNameTypesMap( // same type } } else { - throw new Error("impossible case"); + throw new Error('impossible case'); } } else { // field is missing in target diff --git a/packages/typir/src/utils/utils.ts b/packages/typir/src/utils/utils.ts index a56a8f04..6cc2bfe5 100644 --- a/packages/typir/src/utils/utils.ts +++ b/packages/typir/src/utils/utils.ts @@ -4,8 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type } from "../graph/type-node.js"; -import { Kind } from "../kinds/kind.js"; +import type { Type } from '../graph/type-node.js'; +import type { Kind } from '../kinds/kind.js'; export function assertTrue( condition: boolean, @@ -63,7 +63,7 @@ export function removeFromArray( } export function assertUnreachable(_: never): never { - throw new Error("Error! The input value was not handled."); + throw new Error('Error! The input value was not handled.'); } export function assertKind( diff --git a/packages/typir/test/api-example.test.ts b/packages/typir/test/api-example.test.ts index cff0d33e..bd61e4a6 100644 --- a/packages/typir/test/api-example.test.ts +++ b/packages/typir/test/api-example.test.ts @@ -4,25 +4,23 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -/* eslint-disable @typescript-eslint/parameter-properties */ +import { describe, expect, test } from 'vitest'; +import { InferenceRuleNotApplicable } from '../src/services/inference.js'; +import type { InferOperatorWithMultipleOperands } from '../src/services/operator.js'; +import { createTypirServices } from '../src/typir.js'; -import { describe, expect, test } from "vitest"; -import { InferenceRuleNotApplicable } from "../src/services/inference.js"; -import { InferOperatorWithMultipleOperands } from "../src/services/operator.js"; -import { createTypirServices } from "../src/typir.js"; - -describe("Tiny Typir", () => { - test("Set-up and test some expressions", async () => { +describe('Tiny Typir', () => { + test('Set-up and test some expressions', async () => { const typir = createTypirServices(); // set-up the type system, specifies the root type of all language nodes // primitive types const numberType = typir.factory.Primitives.create({ - primitiveName: "number", + primitiveName: 'number', }) .inferenceRule({ filter: (node) => node instanceof NumberLiteral }) .finish(); const stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral }) .finish(); @@ -38,7 +36,7 @@ describe("Tiny Typir", () => { validateArgumentsOfCalls: true, // explicitly request to check, that the types of the arguments in operator calls fit to the parameters }; typir.factory.Operators.createBinary({ - name: "+", + name: '+', signatures: [ // operator overloading { left: numberType, right: numberType, return: numberType }, // 2 + 3 @@ -48,7 +46,7 @@ describe("Tiny Typir", () => { .inferenceRule(inferenceRule) .finish(); typir.factory.Operators.createBinary({ - name: "-", + name: '-', signatures: [ { left: numberType, right: numberType, return: numberType }, ], @@ -60,7 +58,7 @@ describe("Tiny Typir", () => { typir.Conversion.markAsConvertible( numberType, stringType, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); // specify, how Typir can detect the type of a variable @@ -88,7 +86,7 @@ describe("Tiny Typir", () => { // 2 + 3 => OK const example1 = new BinaryExpression( new NumberLiteral(2), - "+", + '+', new NumberLiteral(3), ); expect(typir.validation.Collector.validate(example1)).toHaveLength(0); @@ -96,16 +94,16 @@ describe("Tiny Typir", () => { // 2 + "3" => OK const example2 = new BinaryExpression( new NumberLiteral(2), - "+", - new StringLiteral("3"), + '+', + new StringLiteral('3'), ); expect(typir.validation.Collector.validate(example2)).toHaveLength(0); // 2 - "3" => wrong const example3 = new BinaryExpression( new NumberLiteral(2), - "-", - new StringLiteral("3"), + '-', + new StringLiteral('3'), ); const errors1 = typir.validation.Collector.validate(example3); const errorStack = typir.Printer.printTypirProblem(errors1[0]); // the problem comes with "sub-problems" to describe the reasons in more detail @@ -117,7 +115,7 @@ describe("Tiny Typir", () => { ); // 123 is assignable to a string variable - const varString = new Variable("v1", new StringLiteral("Hello")); + const varString = new Variable('v1', new StringLiteral('Hello')); const assignNumberToString = new AssignmentStatement( varString, new NumberLiteral(123), @@ -127,10 +125,10 @@ describe("Tiny Typir", () => { ).toHaveLength(0); // "123" is not assignable to a number variable - const varNumber = new Variable("v2", new NumberLiteral(456)); + const varNumber = new Variable('v2', new NumberLiteral(456)); const assignStringToNumber = new AssignmentStatement( varNumber, - new StringLiteral("123"), + new StringLiteral('123'), ); const errors2 = typir.validation.Collector.validate(assignStringToNumber); diff --git a/packages/typir/test/kinds/class/class.test.ts b/packages/typir/test/kinds/class/class.test.ts index 29d72fbd..9d9cb894 100644 --- a/packages/typir/test/kinds/class/class.test.ts +++ b/packages/typir/test/kinds/class/class.test.ts @@ -4,70 +4,70 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, test } from "vitest"; -import { isClassType } from "../../../src/kinds/class/class-type.js"; -import { isPrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +import { describe, test } from 'vitest'; +import { isClassType } from '../../../src/kinds/class/class-type.js'; +import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; import { BooleanLiteral, ClassConstructorCall, ClassFieldAccess, IntegerLiteral, Variable, -} from "../../../src/test/predefined-language-nodes.js"; +} from '../../../src/test/predefined-language-nodes.js'; import { createTypirServicesForTesting, expectToBeType, expectTypirTypes, expectValidationIssuesStrict, -} from "../../../src/utils/test-utils.js"; -import { assertTypirType } from "../../../src/utils/utils.js"; +} from '../../../src/utils/test-utils.js'; +import { assertTypirType } from '../../../src/utils/utils.js'; -describe("Tests some details for class types", () => { - test("create primitive and get it by name", () => { +describe('Tests some details for class types', () => { + test('create primitive and get it by name', () => { const typir = createTypirServicesForTesting(); const classType1 = typir.factory.Classes.create({ - className: "MyClass1", + className: 'MyClass1', fields: [], methods: [], }) .finish() .getTypeFinal(); // since this class has no delayed dependencies, the new class type is directly available! - assertTypirType(classType1, isClassType, "MyClass1"); - expectTypirTypes(typir, isClassType, "MyClass1"); + assertTypirType(classType1, isClassType, 'MyClass1'); + expectTypirTypes(typir, isClassType, 'MyClass1'); }); - test("infer types of accessed fields of a class (and validate them)", () => { + test('infer types of accessed fields of a class (and validate them)', () => { const typir = createTypirServicesForTesting(); const integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); const booleanType = typir.factory.Primitives.create({ - primitiveName: "boolean", + primitiveName: 'boolean', }) .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) .finish(); const classType1 = typir.factory.Classes // a class with two fields with different primitive types .create({ - className: "MyClass1", + className: 'MyClass1', fields: [ - { name: "fieldInteger", type: integerType }, - { name: "fieldBoolean", type: booleanType }, + { name: 'fieldInteger', type: integerType }, + { name: 'fieldBoolean', type: booleanType }, ], methods: [], }) // infer the type for constructor calls .inferenceRuleForClassLiterals({ filter: (node) => node instanceof ClassConstructorCall, - matching: (node) => node.className === "MyClass1", + matching: (node) => node.className === 'MyClass1', inputValuesForFields: (_node) => new Map(), // a useless validation just for testing validation: (node, classType, accept, _typir) => accept({ languageNode: node, - severity: "error", + severity: 'error', message: `Called constructor for '${classType.getName()}'.`, }), }) @@ -83,10 +83,10 @@ describe("Tests some details for class types", () => { field: (node) => node.fieldName, // a useless validation just for testing validation: (node, classType, accept) => { - if (node.fieldName === "fieldBoolean") { + if (node.fieldName === 'fieldBoolean') { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `Validated access of 'fieldBoolean' of the variable '${node.classVariable.name}'.`, }); } @@ -94,7 +94,7 @@ describe("Tests some details for class types", () => { }) .finish() .getTypeFinal(); - assertTypirType(classType1, isClassType, "MyClass1"); + assertTypirType(classType1, isClassType, 'MyClass1'); typir.Inference.addInferenceRule( (node: Variable) => node.initialValue, { languageKey: Variable.name }, @@ -102,8 +102,8 @@ describe("Tests some details for class types", () => { // var1 := new MyClass1(); const varClass = new Variable( - "var1", - new ClassConstructorCall("MyClass1"), + 'var1', + new ClassConstructorCall('MyClass1'), ); expectValidationIssuesStrict(typir, varClass.initialValue, [ "Called constructor for 'MyClass1'.", @@ -111,13 +111,13 @@ describe("Tests some details for class types", () => { // var2 := var1.fieldInteger; const varFieldIntegerValue = new Variable( - "var2", - new ClassFieldAccess(varClass, "fieldInteger"), + 'var2', + new ClassFieldAccess(varClass, 'fieldInteger'), ); expectToBeType( typir.Inference.inferType(varFieldIntegerValue), isPrimitiveType, - (type) => type.getName() === "integer", + (type) => type.getName() === 'integer', ); expectValidationIssuesStrict( typir, @@ -127,13 +127,13 @@ describe("Tests some details for class types", () => { // var3 := var1.fieldBoolean; const varFieldBooleanValue = new Variable( - "var3", - new ClassFieldAccess(varClass, "fieldBoolean"), + 'var3', + new ClassFieldAccess(varClass, 'fieldBoolean'), ); expectToBeType( typir.Inference.inferType(varFieldBooleanValue), isPrimitiveType, - (type) => type.getName() === "boolean", + (type) => type.getName() === 'boolean', ); expectValidationIssuesStrict(typir, varFieldBooleanValue.initialValue, [ "Validated access of 'fieldBoolean' of the variable 'var1'.", diff --git a/packages/typir/test/kinds/function/operator-inference-call.test.ts b/packages/typir/test/kinds/function/operator-inference-call.test.ts index d3f09ab8..546639aa 100644 --- a/packages/typir/test/kinds/function/operator-inference-call.test.ts +++ b/packages/typir/test/kinds/function/operator-inference-call.test.ts @@ -4,12 +4,14 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from "vitest"; -import { isType } from "../../../src/graph/type-node.js"; -import { - isPrimitiveType, - PrimitiveType, -} from "../../../src/kinds/primitive/primitive-type.js"; +import { beforeAll, describe, expect, test } from 'vitest'; +import { isType } from '../../../src/graph/type-node.js'; +import type { PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import type { + TestExpressionNode, + TestLanguageNode, +} from '../../../src/test/predefined-language-nodes.js'; import { BinaryExpression, InferenceRuleBinaryExpression, @@ -19,18 +21,16 @@ import { string123, string456, StringLiteral, - TestExpressionNode, - TestLanguageNode, -} from "../../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../../src/typir.js"; +} from '../../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../../src/typir.js'; import { createTypirServicesForTesting, expectToBeType, expectValidationIssuesStrict, -} from "../../../src/utils/test-utils.js"; +} from '../../../src/utils/test-utils.js'; -describe("Tests some special cases for (overloaded) operator calls", () => { - describe("Overloaded operators, one signature has no arguments at all (this is tests explicitly cover a found bug)", () => { +describe('Tests some special cases for (overloaded) operator calls', () => { + describe('Overloaded operators, one signature has no arguments at all (this is tests explicitly cover a found bug)', () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -40,14 +40,14 @@ describe("Tests some special cases for (overloaded) operator calls", () => { // primitive types integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral, }) .finish(); stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral, @@ -57,7 +57,7 @@ describe("Tests some special cases for (overloaded) operator calls", () => { // + operator: is overloaded // integers, without arguments typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: integerType, right: integerType, @@ -72,7 +72,7 @@ describe("Tests some special cases for (overloaded) operator calls", () => { .finish(); // strings, with arguments typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: stringType, right: stringType, @@ -83,27 +83,27 @@ describe("Tests some special cases for (overloaded) operator calls", () => { .finish(); }); - test("+ with Strings", () => { - expectInferredType(typir, string123, "+", string456, "string"); + test('+ with Strings', () => { + expectInferredType(typir, string123, '+', string456, 'string'); }); - test("+ with Integers", () => { + test('+ with Integers', () => { // fails, since the signature expects two arguments, but the inference rule provides no arguments - expectInferenceProblem(typir, integer123, "+", integer456); + expectInferenceProblem(typir, integer123, '+', integer456); }); }); - test("Overloaded operator has two inference rules", () => { + test('Overloaded operator has two inference rules', () => { const typir = createTypirServicesForTesting(); // primitive types const integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); const stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral }) .finish(); @@ -111,7 +111,7 @@ describe("Tests some special cases for (overloaded) operator calls", () => { // + operator: is overloaded // integers, without arguments typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: integerType, right: integerType, @@ -126,7 +126,7 @@ describe("Tests some special cases for (overloaded) operator calls", () => { .finish(); // strings, with arguments typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: stringType, right: stringType, @@ -137,20 +137,20 @@ describe("Tests some special cases for (overloaded) operator calls", () => { .finish(); // with the second inference rule, it works now as usual! - expectInferredType(typir, integer123, "+", integer456, "integer"); + expectInferredType(typir, integer123, '+', integer456, 'integer'); }); - test("Overloaded operator has a validation for the inference rule of one signature", () => { + test('Overloaded operator has a validation for the inference rule of one signature', () => { const typir = createTypirServicesForTesting(); // primitive types const integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); const stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral }) .finish(); @@ -158,7 +158,7 @@ describe("Tests some special cases for (overloaded) operator calls", () => { // + operator: is overloaded // integers, with validation typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: integerType, right: integerType, @@ -170,14 +170,14 @@ describe("Tests some special cases for (overloaded) operator calls", () => { validation: (node, name, opType, accept) => accept({ languageNode: node, - severity: "error", + severity: 'error', message: `Called '${name}' with '${opType.getOutput()!.type.getName()}'.`, }), }) .finish(); // strings, without validation typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: stringType, right: stringType, @@ -190,12 +190,12 @@ describe("Tests some special cases for (overloaded) operator calls", () => { // validation issues only for one of the two signatures! expectValidationIssuesStrict( typir, - new BinaryExpression(integer123, "+", integer456), + new BinaryExpression(integer123, '+', integer456), ["Called '+' with 'integer'."], ); expectValidationIssuesStrict( typir, - new BinaryExpression(string123, "+", string456), + new BinaryExpression(string123, '+', string456), [], ); }); @@ -204,9 +204,9 @@ describe("Tests some special cases for (overloaded) operator calls", () => { function expectInferredType( typir: TypirServices, left: TestExpressionNode, - operator: "+", + operator: '+', right: TestExpressionNode, - expectedType: "integer" | "string", + expectedType: 'integer' | 'string', ): void { const expr = new BinaryExpression(left, operator, right); const result = typir.Inference.inferType(expr); @@ -218,7 +218,7 @@ function expectInferredType( ); } else { expect.fail( - result.map((p) => typir.Printer.printTypirProblem(p)).join("\n"), + result.map((p) => typir.Printer.printTypirProblem(p)).join('\n'), ); } } @@ -226,7 +226,7 @@ function expectInferredType( function expectInferenceProblem( typir: TypirServices, left: TestExpressionNode, - operator: "+", + operator: '+', right: TestExpressionNode, ): void { const expr = new BinaryExpression(left, operator, right); diff --git a/packages/typir/test/kinds/function/operator-overloaded.test.ts b/packages/typir/test/kinds/function/operator-overloaded.test.ts index b020019a..b0ef9cf1 100644 --- a/packages/typir/test/kinds/function/operator-overloaded.test.ts +++ b/packages/typir/test/kinds/function/operator-overloaded.test.ts @@ -4,19 +4,21 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from "vitest"; -import { Type } from "../../../src/graph/type-node.js"; -import { - isPrimitiveType, - PrimitiveType, -} from "../../../src/kinds/primitive/primitive-type.js"; +import { beforeAll, describe, expect, test } from 'vitest'; +import type { Type } from '../../../src/graph/type-node.js'; +import type { PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; import { isAssignabilityProblem, isAssignabilitySuccess, -} from "../../../src/services/assignability.js"; -import { ConversionEdge } from "../../../src/services/conversion.js"; -import { InferenceRuleNotApplicable } from "../../../src/services/inference.js"; -import { SubTypeEdge } from "../../../src/services/subtype.js"; +} from '../../../src/services/assignability.js'; +import type { ConversionEdge } from '../../../src/services/conversion.js'; +import { InferenceRuleNotApplicable } from '../../../src/services/inference.js'; +import type { SubTypeEdge } from '../../../src/services/subtype.js'; +import type { + TestExpressionNode, + TestLanguageNode, +} from '../../../src/test/predefined-language-nodes.js'; import { AssignmentStatement, BinaryExpression, @@ -33,18 +35,16 @@ import { string2, string3, StringLiteral, - TestExpressionNode, - TestLanguageNode, Variable, -} from "../../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../../src/typir.js"; +} from '../../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../../src/typir.js'; import { createTypirServicesForTesting, expectToBeType, -} from "../../../src/utils/test-utils.js"; -import { assertTrue } from "../../../src/utils/utils.js"; +} from '../../../src/utils/test-utils.js'; +import { assertTrue } from '../../../src/utils/utils.js'; -describe("Multiple best matches for overloaded operators", () => { +describe('Multiple best matches for overloaded operators', () => { let typir: TypirServices; let integerType: PrimitiveType; let doubleType: PrimitiveType; @@ -56,29 +56,29 @@ describe("Multiple best matches for overloaded operators", () => { // primitive types integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); doubleType = typir.factory.Primitives.create({ - primitiveName: "double", + primitiveName: 'double', }) .inferenceRule({ filter: (node) => node instanceof DoubleLiteral }) .finish(); stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral }) .finish(); booleanType = typir.factory.Primitives.create({ - primitiveName: "boolean", + primitiveName: 'boolean', }) .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) .finish(); // operators typir.factory.Operators.createBinary({ - name: "+", + name: '+', signatures: [ // operator overloading { left: integerType, right: integerType, return: integerType }, // 2 + 3 => 5 @@ -94,13 +94,13 @@ describe("Multiple best matches for overloaded operators", () => { typir.Conversion.markAsConvertible( booleanType, integerType, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); // integerVariable := booleanValue; typir.Subtype.markAsSubType(integerType, doubleType); // double <|--- integer typir.Conversion.markAsConvertible( doubleType, stringType, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); // stringVariable := doubleValue; // specify, how Typir can detect the type of a variable @@ -126,72 +126,72 @@ describe("Multiple best matches for overloaded operators", () => { }); }); - describe("tests all cases for assignability and the checks the found assignability paths", () => { - test("integer to integer", () => { + describe('tests all cases for assignability and the checks the found assignability paths', () => { + test('integer to integer', () => { expectAssignmentValid(integerType, integerType); }); - test("double to integer", () => { + test('double to integer', () => { expectAssignmentError(doubleType, integerType); }); - test("string to integer", () => { + test('string to integer', () => { expectAssignmentError(stringType, integerType); }); - test("boolean to integer", () => { - expectAssignmentValid(booleanType, integerType, "ConversionEdge"); + test('boolean to integer', () => { + expectAssignmentValid(booleanType, integerType, 'ConversionEdge'); }); - test("integer to double", () => { - expectAssignmentValid(integerType, doubleType, "SubTypeEdge"); + test('integer to double', () => { + expectAssignmentValid(integerType, doubleType, 'SubTypeEdge'); }); - test("double to double", () => { + test('double to double', () => { expectAssignmentValid(doubleType, doubleType); }); - test("string to double", () => { + test('string to double', () => { expectAssignmentError(stringType, doubleType); }); - test("boolean to double", () => { + test('boolean to double', () => { expectAssignmentValid( booleanType, doubleType, - "ConversionEdge", - "SubTypeEdge", + 'ConversionEdge', + 'SubTypeEdge', ); }); - test("integer to string", () => { + test('integer to string', () => { expectAssignmentValid( integerType, stringType, - "SubTypeEdge", - "ConversionEdge", + 'SubTypeEdge', + 'ConversionEdge', ); }); - test("double to string", () => { - expectAssignmentValid(doubleType, stringType, "ConversionEdge"); + test('double to string', () => { + expectAssignmentValid(doubleType, stringType, 'ConversionEdge'); }); - test("string to string", () => { + test('string to string', () => { expectAssignmentValid(stringType, stringType); }); - test("boolean to string", () => { + test('boolean to string', () => { expectAssignmentValid( booleanType, stringType, - "ConversionEdge", - "SubTypeEdge", - "ConversionEdge", + 'ConversionEdge', + 'SubTypeEdge', + 'ConversionEdge', ); }); - test("integer to boolean", () => { + test('integer to boolean', () => { expectAssignmentError(integerType, booleanType); }); - test("double to boolean", () => { + test('double to boolean', () => { expectAssignmentError(doubleType, booleanType); }); - test("string to boolean", () => { + test('string to boolean', () => { expectAssignmentError(stringType, booleanType); }); - test("boolean to boolean", () => { + test('boolean to boolean', () => { expectAssignmentValid(booleanType, booleanType); }); @@ -199,7 +199,7 @@ describe("Multiple best matches for overloaded operators", () => { sourceType: Type, targetType: Type, ...expectedPath: Array< - SubTypeEdge["$relation"] | ConversionEdge["$relation"] + SubTypeEdge['$relation'] | ConversionEdge['$relation'] > ): void { // check the resulting assignability path @@ -210,7 +210,7 @@ describe("Multiple best matches for overloaded operators", () => { ); assertTrue(isAssignabilitySuccess(assignabilityResult)); const actualPath = assignabilityResult.path; - const msg = `Actual assignability path is ${actualPath.map((e) => e.$relation).join(" --> ")}.`; + const msg = `Actual assignability path is ${actualPath.map((e) => e.$relation).join(' --> ')}.`; expect(actualPath.length, msg).toBe(expectedPath.length); for (let i = 0; i < actualPath.length; i++) { expect(actualPath[i].$relation, msg).toBe(expectedPath[i]); @@ -239,52 +239,52 @@ describe("Multiple best matches for overloaded operators", () => { } }); - describe("Test multiple matches for overloaded operators and ensures that the best match is chosen", () => { - test("2 + 3 => both are integers", () => { - expectOverload(integer2, integer3, "integer"); + describe('Test multiple matches for overloaded operators and ensures that the best match is chosen', () => { + test('2 + 3 => both are integers', () => { + expectOverload(integer2, integer3, 'integer'); }); - test("2.0 + 3.0 => both are doubles", () => { - expectOverload(double2_0, double3_0, "double"); + test('2.0 + 3.0 => both are doubles', () => { + expectOverload(double2_0, double3_0, 'double'); }); test('"2" + "3" => both are strings', () => { - expectOverload(string2, string3, "string"); + expectOverload(string2, string3, 'string'); }); - test("TRUE + FALSE => both are booleans", () => { - expectOverload(booleanTrue, booleanFalse, "boolean"); + test('TRUE + FALSE => both are booleans', () => { + expectOverload(booleanTrue, booleanFalse, 'boolean'); }); - test("2 + TRUE => convert boolean to integer", () => { - expectOverload(integer2, booleanTrue, "integer"); + test('2 + TRUE => convert boolean to integer', () => { + expectOverload(integer2, booleanTrue, 'integer'); }); - test("2.0 + 3 => integers are doubles", () => { - expectOverload(double2_0, integer3, "double"); + test('2.0 + 3 => integers are doubles', () => { + expectOverload(double2_0, integer3, 'double'); }); test('2.0 + "3" => convert double to string', () => { - expectOverload(double2_0, string3, "string"); + expectOverload(double2_0, string3, 'string'); }); test('2 + "3" => integer is sub-type of double, which is convertible to string', () => { - expectOverload(integer2, string3, "string"); + expectOverload(integer2, string3, 'string'); }); function expectOverload( left: TestExpressionNode, right: TestExpressionNode, - typeName: "string" | "integer" | "double" | "boolean", + typeName: 'string' | 'integer' | 'double' | 'boolean', ): void { - const example = new BinaryExpression(left, "+", right); + const example = new BinaryExpression(left, '+', right); const validationProblems = typir.validation.Collector.validate(example); expect( validationProblems, validationProblems .map((p) => typir.Printer.printValidationProblem(p)) - .join("\n"), + .join('\n'), ).toHaveLength(0); const inferredType = typir.Inference.inferType(example); expectToBeType( diff --git a/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts b/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts index 78eb1341..56ed9405 100644 --- a/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts +++ b/packages/typir/test/kinds/function/operator-validation-call-arguments.test.ts @@ -4,8 +4,12 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, expect, test } from "vitest"; -import { PrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +import { beforeAll, describe, expect, test } from 'vitest'; +import type { PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import type { + TestExpressionNode, + TestLanguageNode, +} from '../../../src/test/predefined-language-nodes.js'; import { BinaryExpression, booleanFalse, @@ -15,11 +19,9 @@ import { integer123, integer456, IntegerLiteral, - TestExpressionNode, - TestLanguageNode, -} from "../../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../../src/typir.js"; -import { createTypirServicesForTesting } from "../../../src/utils/test-utils.js"; +} from '../../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../../src/typir.js'; +import { createTypirServicesForTesting } from '../../../src/utils/test-utils.js'; describe('Tests the "validateArgumentsOfCalls" option to check the given arguments in (overloaded) operator calls', () => { let typir: TypirServices; @@ -31,19 +33,19 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen // primitive types integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); booleanType = typir.factory.Primitives.create({ - primitiveName: "boolean", + primitiveName: 'boolean', }) .inferenceRule({ filter: (node) => node instanceof BooleanLiteral }) .finish(); // + operator: only integers, validate it typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: integerType, right: integerType, @@ -58,7 +60,7 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen // && operator: only booleans, don't validate it typir.factory.Operators.createBinary({ - name: "&&", + name: '&&', signature: { left: booleanType, right: booleanType, @@ -73,7 +75,7 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen // == operator: is overloaded, validate the integer signature, don't validate the boolean signature typir.factory.Operators.createBinary({ - name: "==", + name: '==', signature: { left: integerType, right: integerType, @@ -86,7 +88,7 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen }) .finish(); typir.factory.Operators.createBinary({ - name: "==", + name: '==', signature: { left: booleanType, right: booleanType, @@ -101,53 +103,53 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen }); // +: only integers are supported - test("123 + 456: OK", () => { - expectOperatorCallValid(integer123, "+", integer456); + test('123 + 456: OK', () => { + expectOperatorCallValid(integer123, '+', integer456); }); - test("true + false: wrong, since this signature does not exist", () => { + test('true + false: wrong, since this signature does not exist', () => { expectOperatorCallError( booleanTrue, - "+", + '+', booleanFalse, "The type 'boolean' is not assignable to the type 'integer'.", ); }); // &&: only booleans are supported - test("123 && 456: not OK, but no errors are shown, since the validation is switched off for &&", () => { - expectOperatorCallValid(integer123, "&&", integer456); + test('123 && 456: not OK, but no errors are shown, since the validation is switched off for &&', () => { + expectOperatorCallValid(integer123, '&&', integer456); }); - test("true && false: OK", () => { - expectOperatorCallValid(booleanTrue, "&&", booleanFalse); + test('true && false: OK', () => { + expectOperatorCallValid(booleanTrue, '&&', booleanFalse); }); // ==: both signatures are supported, but only one is validated - test("123 == 456: OK and validated", () => { - expectOperatorCallValid(integer123, "==", integer456); + test('123 == 456: OK and validated', () => { + expectOperatorCallValid(integer123, '==', integer456); }); - test("true == false: OK, since the signature exists", () => { - expectOperatorCallValid(booleanTrue, "==", booleanFalse); + test('true == false: OK, since the signature exists', () => { + expectOperatorCallValid(booleanTrue, '==', booleanFalse); }); - test("123 == false: wrong, since this signature is not defined", () => { + test('123 == false: wrong, since this signature is not defined', () => { expectOperatorCallError( integer123, - "==", + '==', booleanFalse, - "is not assignable to the type", + 'is not assignable to the type', ); }); - test("true == 456: wrong, since this signature is not defined", () => { + test('true == 456: wrong, since this signature is not defined', () => { expectOperatorCallError( booleanTrue, - "==", + '==', integer456, - "is not assignable to the type", + 'is not assignable to the type', ); }); function expectOperatorCallValid( left: TestExpressionNode, - operator: "==" | "+" | "&&", + operator: '==' | '+' | '&&', right: TestExpressionNode, ): void { const expr = new BinaryExpression(left, operator, right); @@ -156,7 +158,7 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen } function expectOperatorCallError( left: TestExpressionNode, - operator: "==" | "+" | "&&", + operator: '==' | '+' | '&&', right: TestExpressionNode, includedProblem: string, ): void { diff --git a/packages/typir/test/kinds/primitive/primitive.test.ts b/packages/typir/test/kinds/primitive/primitive.test.ts index d92e351e..0bd1f945 100644 --- a/packages/typir/test/kinds/primitive/primitive.test.ts +++ b/packages/typir/test/kinds/primitive/primitive.test.ts @@ -4,67 +4,67 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeEach, describe, expect, test } from "vitest"; +import { beforeEach, describe, expect, test } from 'vitest'; import { createTypirServicesForTesting, expectTypirTypes, -} from "../../../src/utils/test-utils.js"; -import { assertTypirType } from "../../../src/utils/utils.js"; -import { isPrimitiveType } from "../../../src/kinds/primitive/primitive-type.js"; +} from '../../../src/utils/test-utils.js'; +import { assertTypirType } from '../../../src/utils/utils.js'; +import { isPrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import type { TestLanguageNode } from '../../../src/test/predefined-language-nodes.js'; import { integer123, IntegerLiteral, stringHello, StringLiteral, - TestLanguageNode, -} from "../../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../../src/typir.js"; +} from '../../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../../src/typir.js'; -describe("Tests some details for primitive types", () => { - test("create primitive and get it by name", () => { +describe('Tests some details for primitive types', () => { + test('create primitive and get it by name', () => { const typir = createTypirServicesForTesting(); const integerType1 = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }).finish(); - assertTypirType(integerType1, isPrimitiveType, "integer"); - expectTypirTypes(typir, isPrimitiveType, "integer"); + assertTypirType(integerType1, isPrimitiveType, 'integer'); + expectTypirTypes(typir, isPrimitiveType, 'integer'); const integerType2 = typir.factory.Primitives.get({ - primitiveName: "integer", + primitiveName: 'integer', }); - assertTypirType(integerType2, isPrimitiveType, "integer"); + assertTypirType(integerType2, isPrimitiveType, 'integer'); expect(integerType1).toBe(integerType2); }); - test("error when trying to create the same primitive twice", () => { + test('error when trying to create the same primitive twice', () => { const typir = createTypirServicesForTesting(); // create the 1st integer const integerType1 = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }).finish(); - assertTypirType(integerType1, isPrimitiveType, "integer"); + assertTypirType(integerType1, isPrimitiveType, 'integer'); // creating the 2nd integer will fail expect(() => typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }).finish(), ).toThrowError(); }); - describe("Test validation for inference rule of a primitive type", () => { + describe('Test validation for inference rule of a primitive type', () => { let typir: TypirServices; beforeEach(() => { typir = createTypirServicesForTesting(); // create a primitive type with some inference rules - typir.factory.Primitives.create({ primitiveName: "integer" }) + typir.factory.Primitives.create({ primitiveName: 'integer' }) .inferenceRule({ // 1st rule for IntegerLiterals, with validation languageKey: IntegerLiteral.name, validation: (node: IntegerLiteral, type, accept) => accept({ - message: "integer-validation", + message: 'integer-validation', languageNode: node, - severity: "error", + severity: 'error', }), }) .inferenceRule({ @@ -74,22 +74,22 @@ describe("Tests some details for primitive types", () => { .finish(); }); - test("Integer value with validation issues", () => { + test('Integer value with validation issues', () => { assertTypirType( typir.Inference.inferType(integer123), isPrimitiveType, - "integer", + 'integer', ); // test the successful inference const result = typir.validation.Collector.validate(integer123); // check that a validation issue is produced expect(result).toHaveLength(1); - expect(result[0].message).toBe("integer-validation"); + expect(result[0].message).toBe('integer-validation'); }); - test("String value without validation issue", () => { + test('String value without validation issue', () => { assertTypirType( typir.Inference.inferType(stringHello), isPrimitiveType, - "integer", + 'integer', ); // test the successful inference const result = typir.validation.Collector.validate(stringHello); // check that no validation issue is produced expect(result).toHaveLength(0); diff --git a/packages/typir/test/services/conversion.test.ts b/packages/typir/test/services/conversion.test.ts index 8110a444..390c9895 100644 --- a/packages/typir/test/services/conversion.test.ts +++ b/packages/typir/test/services/conversion.test.ts @@ -4,33 +4,33 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { describe, expect, test } from "vitest"; -import { createTypirServicesForTesting } from "../../src/utils/test-utils.js"; +import { describe, expect, test } from 'vitest'; +import { createTypirServicesForTesting } from '../../src/utils/test-utils.js'; -describe("Testing conversion", () => { - test("exception in case of cyclic conversion rules", () => { +describe('Testing conversion', () => { + test('exception in case of cyclic conversion rules', () => { const typir = createTypirServicesForTesting(); const integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }).finish(); const doubleType = typir.factory.Primitives.create({ - primitiveName: "double", + primitiveName: 'double', }).finish(); // define cyclic relationships between types typir.Conversion.markAsConvertible( integerType, doubleType, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); expect(() => typir.Conversion.markAsConvertible( doubleType, integerType, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ), ).toThrowError( - "Adding the conversion from double to integer with mode IMPLICIT_EXPLICIT has introduced a cycle in the type graph.", + 'Adding the conversion from double to integer with mode IMPLICIT_EXPLICIT has introduced a cycle in the type graph.', ); }); }); diff --git a/packages/typir/test/services/inference-registry.test.ts b/packages/typir/test/services/inference-registry.test.ts index ca4c42b0..4f352e31 100644 --- a/packages/typir/test/services/inference-registry.test.ts +++ b/packages/typir/test/services/inference-registry.test.ts @@ -4,31 +4,34 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeEach, describe, expect, test } from "vitest"; -import { isType, Type } from "../../src/graph/type-node.js"; -import { PrimitiveType } from "../../src/kinds/primitive/primitive-type.js"; +import { beforeEach, describe, expect, test } from 'vitest'; +import type { Type } from '../../src/graph/type-node.js'; +import { isType } from '../../src/graph/type-node.js'; +import type { PrimitiveType } from '../../src/kinds/primitive/primitive-type.js'; +import type { + TypeInferenceRule, + TypeInferenceRuleWithoutInferringChildren, +} from '../../src/services/inference.js'; import { CompositeTypeInferenceRule, DefaultTypeInferenceCollector, InferenceProblem, InferenceRuleNotApplicable, - TypeInferenceRule, - TypeInferenceRuleWithoutInferringChildren, -} from "../../src/services/inference.js"; -import { ValidationRuleOptions } from "../../src/services/validation.js"; +} from '../../src/services/inference.js'; +import type { ValidationRuleOptions } from '../../src/services/validation.js'; +import type { TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; import { booleanFalse, integer123, IntegerLiteral, stringHello, StringLiteral, - TestLanguageNode, -} from "../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../src/typir.js"; -import { RuleRegistry } from "../../src/utils/rule-registration.js"; -import { createTypirServicesForTesting } from "../../src/utils/test-utils.js"; +} from '../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../src/typir.js'; +import type { RuleRegistry } from '../../src/utils/rule-registration.js'; +import { createTypirServicesForTesting } from '../../src/utils/test-utils.js'; -describe("Tests the logic for registering rules (applied to inference rules)", () => { +describe('Tests the logic for registering rules (applied to inference rules)', () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -36,7 +39,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( let ruleString: TypeInferenceRuleWithoutInferringChildren; let ruleInteger: TypeInferenceRuleWithoutInferringChildren; let ruleStringInteger: TypeInferenceRuleWithoutInferringChildren; - const NOT_FOUND = "found no applicable inference rules"; + const NOT_FOUND = 'found no applicable inference rules'; beforeEach(() => { // Typir services @@ -46,10 +49,10 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( // primitive types integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }).finish(); stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }).finish(); // composite inference rules @@ -79,22 +82,22 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( return { $problem: InferenceProblem, languageNode: node, - location: "failure3-" + node.print(), + location: 'failure3-' + node.print(), subProblems: [], }; } }; }); - describe("Simple inference rules", () => { - test("add String rule without any options", () => { + describe('Simple inference rules', () => { + test('add String rule without any options', () => { assertNumberRules(0); addInferenceRule(ruleString); assertNumberRules(1); infer(stringHello, stringType); }); - test("remove String rule without any options", () => { + test('remove String rule without any options', () => { addInferenceRule(ruleString); infer(stringHello, stringType); removeInferenceRule(ruleString); @@ -102,7 +105,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(stringHello, NOT_FOUND); }); - test("add rule for Strings only", () => { + test('add rule for Strings only', () => { addInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name, }); @@ -111,7 +114,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(booleanFalse, NOT_FOUND); }); - test("add rule for String and Integer", () => { + test('add rule for String and Integer', () => { addInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name, }); @@ -126,7 +129,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(booleanFalse, NOT_FOUND); }); - test("remove rule", () => { + test('remove rule', () => { addInferenceRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name], }); @@ -148,15 +151,15 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( }); }); - describe("Composite inference rule", () => { - test("add String rule to composite", () => { + describe('Composite inference rule', () => { + test('add String rule to composite', () => { assertNumberRules(0); composite.addInferenceRule(ruleString); assertNumberRules(1); infer(stringHello, stringType); }); - test("remove String rule from composite", () => { + test('remove String rule from composite', () => { composite.addInferenceRule(ruleString); infer(stringHello, stringType); composite.removeInferenceRule(ruleString); @@ -180,7 +183,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(integer123, NOT_FOUND); }); - test("remove rule with multiple keys from composite", () => { + test('remove rule with multiple keys from composite', () => { composite.addInferenceRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name], }); @@ -201,7 +204,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(integer123, NOT_FOUND); }); - test("remove rules which are bound to types from composite", () => { + test('remove rules which are bound to types from composite', () => { assertNumberRules(0); composite.addInferenceRule(ruleString, { boundToType: stringType }); assertNumberRules(1); @@ -223,7 +226,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( infer(integer123, NOT_FOUND); }); - test("remove rule which is bound to types from composite", () => { + test('remove rule which is bound to types from composite', () => { assertNumberRules(0); composite.addInferenceRule(ruleStringInteger, { boundToType: [stringType, integerType], @@ -284,7 +287,7 @@ describe("Tests the logic for registering rules (applied to inference rules)", ( } else { const actualProblems = actual .map((a) => typir.Printer.printTypirProblem(a)) - .join("\n"); + .join('\n'); if (isType(expected)) { expect.fail( `Got error "${actualProblems}", but expected type '${expected.getName()}'.`, diff --git a/packages/typir/test/services/validation-registry.test.ts b/packages/typir/test/services/validation-registry.test.ts index 498d3aa0..12d4eaef 100644 --- a/packages/typir/test/services/validation-registry.test.ts +++ b/packages/typir/test/services/validation-registry.test.ts @@ -4,32 +4,32 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeEach, describe, expect, test } from "vitest"; -import { Type } from "../../src/graph/type-node.js"; -import { PrimitiveType } from "../../src/kinds/primitive/primitive-type.js"; -import { - DefaultValidationCollector, +import { beforeEach, describe, expect, test } from 'vitest'; +import type { Type } from '../../src/graph/type-node.js'; +import type { PrimitiveType } from '../../src/kinds/primitive/primitive-type.js'; +import type { ValidationRule, ValidationRuleFunctional, ValidationRuleLifecycle, ValidationRuleOptions, -} from "../../src/services/validation.js"; +} from '../../src/services/validation.js'; +import { DefaultValidationCollector } from '../../src/services/validation.js'; +import type { TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; import { booleanTrue, integer123, IntegerLiteral, stringHello, StringLiteral, - TestLanguageNode, -} from "../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../src/typir.js"; -import { RuleRegistry } from "../../src/utils/rule-registration.js"; +} from '../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../src/typir.js'; +import type { RuleRegistry } from '../../src/utils/rule-registration.js'; import { createTypirServicesForTesting, expectValidationIssuesStrict, -} from "../../src/utils/test-utils.js"; +} from '../../src/utils/test-utils.js'; -describe("Tests the logic for registering rules (applied to state-less validation rules)", () => { +describe('Tests the logic for registering rules (applied to state-less validation rules)', () => { let typir: TypirServices; let integerType: PrimitiveType; let stringType: PrimitiveType; @@ -47,12 +47,12 @@ describe("Tests the logic for registering rules (applied to state-less validatio // primitive types integerType = typir.factory.Primitives.create({ - primitiveName: "integer", + primitiveName: 'integer', }) .inferenceRule({ filter: (node) => node instanceof IntegerLiteral }) .finish(); stringType = typir.factory.Primitives.create({ - primitiveName: "string", + primitiveName: 'string', }) .inferenceRule({ filter: (node) => node instanceof StringLiteral }) .finish(); @@ -62,7 +62,7 @@ describe("Tests the logic for registering rules (applied to state-less validatio if (node instanceof StringLiteral) { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `s1-${node.value}`, }); } @@ -71,7 +71,7 @@ describe("Tests the logic for registering rules (applied to state-less validatio if (node instanceof IntegerLiteral) { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `i2-${node.value}`, }); } @@ -80,117 +80,117 @@ describe("Tests the logic for registering rules (applied to state-less validatio if (node instanceof StringLiteral) { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `s3-${node.value}`, }); } else if (node instanceof IntegerLiteral) { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `i3-${node.value}`, }); } else { accept({ languageNode: node, - severity: "error", + severity: 'error', message: `failure3-${node.constructor.name}`, }); } }; }); - describe("Add validation rules with different language keys", () => { - test("String rule without any options", () => { + describe('Add validation rules with different language keys', () => { + test('String rule without any options', () => { addValidationRule(ruleString, {}); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String rule registered for String", () => { + test('String rule registered for String', () => { addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String rule registered for Integer => no validation issues", () => { + test('String rule registered for Integer => no validation issues', () => { addValidationRule(ruleString, { languageKey: IntegerLiteral.name }); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); // integer values are ignored by the rule for strings expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String+Integer rule without any options", () => { + test('String+Integer rule without any options', () => { addValidationRule(ruleStringInteger, {}); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); expectValidationIssuesStrict(typir, booleanTrue, [ - "failure3-BooleanLiteral", + 'failure3-BooleanLiteral', ]); // generic message for everything else than strings and integers }); - test("String+Integer rule registered for String", () => { + test('String+Integer rule registered for String', () => { addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name, }); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); // no messages for not-evaluated validations expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String+Integer rule registered for Integer", () => { + test('String+Integer rule registered for Integer', () => { addValidationRule(ruleStringInteger, { languageKey: IntegerLiteral.name, }); expectValidationIssuesStrict(typir, stringHello, []); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String+Integer rule registered for String and Integer", () => { + test('String+Integer rule registered for String and Integer', () => { addValidationRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name], }); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String rule + Integer rule without any options", () => { + test('String rule + Integer rule without any options', () => { addValidationRule(ruleString, {}); addValidationRule(ruleInteger, {}); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i2-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i2-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String rule + Integer registered for their respective language keys", () => { + test('String rule + Integer registered for their respective language keys', () => { addValidationRule(ruleString, { languageKey: StringLiteral.name }); addValidationRule(ruleInteger, { languageKey: IntegerLiteral.name, }); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i2-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i2-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); - test("String rule + Integer + String+Integer rule without any options", () => { + test('String rule + Integer + String+Integer rule without any options', () => { addValidationRule(ruleString, {}); addValidationRule(ruleInteger, {}); addValidationRule(ruleStringInteger, {}); assertNumberRules(3); expectValidationIssuesStrict(typir, stringHello, [ - "s1-Hello", - "s3-Hello", + 's1-Hello', + 's3-Hello', ]); expectValidationIssuesStrict(typir, integer123, [ - "i2-123", - "i3-123", + 'i2-123', + 'i3-123', ]); expectValidationIssuesStrict(typir, booleanTrue, [ - "failure3-BooleanLiteral", + 'failure3-BooleanLiteral', ]); }); - test("adding different rules", () => { + test('adding different rules', () => { assertNumberRules(0); addValidationRule(ruleString, {}); assertNumberRules(1); @@ -204,17 +204,17 @@ describe("Tests the logic for registering rules (applied to state-less validatio addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name, }); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); addValidationRule(ruleStringInteger, { languageKey: undefined }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); }); }); - describe("Add the same rule multiple times", () => { - test("adding the same rule multiple times", () => { + describe('Add the same rule multiple times', () => { + test('adding the same rule multiple times', () => { assertNumberRules(0); addValidationRule(ruleString, {}); assertNumberRules(1); @@ -224,7 +224,7 @@ describe("Tests the logic for registering rules (applied to state-less validatio assertNumberRules(1); }); - test("Adding the same rule for different language keys", () => { + test('Adding the same rule for different language keys', () => { addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name, }); @@ -233,40 +233,40 @@ describe("Tests the logic for registering rules (applied to state-less validatio languageKey: IntegerLiteral.name, }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); }); - describe("Remove validation rules with different language keys", () => { - test("Removing a rule", () => { + describe('Remove validation rules with different language keys', () => { + test('Removing a rule', () => { expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); removeValidationRule(ruleString, { languageKey: StringLiteral.name, }); expectValidationIssuesStrict(typir, stringHello, []); }); - test("Removing a rule (which was added twice)", () => { + test('Removing a rule (which was added twice)', () => { expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); removeValidationRule(ruleString, { languageKey: StringLiteral.name, }); expectValidationIssuesStrict(typir, stringHello, []); }); - test("Removing a rule more often that it was added is OK", () => { + test('Removing a rule more often that it was added is OK', () => { removeValidationRule(ruleString, { languageKey: StringLiteral.name, }); expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); - expectValidationIssuesStrict(typir, stringHello, ["s1-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); removeValidationRule(ruleString, { languageKey: StringLiteral.name, }); @@ -282,8 +282,8 @@ describe("Tests the logic for registering rules (applied to state-less validatio languageKey: StringLiteral.name, }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); // it is still validated, since the rule is still registed for 'undefined' - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); // it is still validated, since the rule is still registed for 'undefined' + expectValidationIssuesStrict(typir, integer123, ['i3-123']); }); test('Remove the same rule for dedicated language keys and "undefined"', () => { @@ -291,7 +291,7 @@ describe("Tests the logic for registering rules (applied to state-less validatio languageKey: StringLiteral.name, }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); removeValidationRule(ruleStringInteger, { languageKey: undefined }); // the rule is removed for all language keys assertNumberRules(0); @@ -300,25 +300,25 @@ describe("Tests the logic for registering rules (applied to state-less validatio }); }); - describe("bound to type", () => { + describe('bound to type', () => { test('remove bound rule automatically ("undefined" as language key)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); removeType(stringType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); }); - test("remove bound rule automatically (one dedicated language key: String)", () => { + test('remove bound rule automatically (one dedicated language key: String)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name], }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); removeType(stringType); assertNumberRules(0); @@ -326,28 +326,28 @@ describe("Tests the logic for registering rules (applied to state-less validatio expectValidationIssuesStrict(typir, integer123, []); }); - test("remove bound rule automatically (one dedicated language key: Integer)", () => { + test('remove bound rule automatically (one dedicated language key: Integer)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [IntegerLiteral.name], }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, []); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); removeType(stringType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, []); }); - test("remove bound rule automatically (multiple dedicated language keys)", () => { + test('remove bound rule automatically (multiple dedicated language keys)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name, IntegerLiteral.name], }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); assertNumberRules(1); removeType(stringType); // rule is removed for all language keys! assertNumberRules(0); @@ -355,17 +355,17 @@ describe("Tests the logic for registering rules (applied to state-less validatio expectValidationIssuesStrict(typir, integer123, []); }); - test("remove bound rule automatically, when the last type is removed", () => { + test('remove bound rule automatically, when the last type is removed', () => { addValidationRule(ruleStringInteger, { boundToType: [stringType, integerType], }); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); removeType(stringType); assertNumberRules(1); - expectValidationIssuesStrict(typir, stringHello, ["s3-Hello"]); - expectValidationIssuesStrict(typir, integer123, ["i3-123"]); + expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); + expectValidationIssuesStrict(typir, integer123, ['i3-123']); removeType(integerType); assertNumberRules(0); expectValidationIssuesStrict(typir, stringHello, []); diff --git a/packages/typir/test/type-definitions.test.ts b/packages/typir/test/type-definitions.test.ts index 6045442c..1951fe89 100644 --- a/packages/typir/test/type-definitions.test.ts +++ b/packages/typir/test/type-definitions.test.ts @@ -5,63 +5,63 @@ ******************************************************************************/ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, expect, test } from "vitest"; -import { ClassKind } from "../src/kinds/class/class-kind.js"; +import { describe, expect, test } from 'vitest'; +import { ClassKind } from '../src/kinds/class/class-kind.js'; import { FunctionKind, NO_PARAMETER_NAME, -} from "../src/kinds/function/function-kind.js"; -import { PrimitiveKind } from "../src/kinds/primitive/primitive-kind.js"; -import { createTypirServices } from "../src/typir.js"; -import { MultiplicityKind } from "../src/kinds/multiplicity/multiplicity-kind.js"; -import { FixedParameterKind } from "../src/kinds/fixed-parameters/fixed-parameters-kind.js"; +} from '../src/kinds/function/function-kind.js'; +import { PrimitiveKind } from '../src/kinds/primitive/primitive-kind.js'; +import { createTypirServices } from '../src/typir.js'; +import { MultiplicityKind } from '../src/kinds/multiplicity/multiplicity-kind.js'; +import { FixedParameterKind } from '../src/kinds/fixed-parameters/fixed-parameters-kind.js'; -describe("Tests for Typir", () => { - test("Define some types", async () => { +describe('Tests for Typir', () => { + test('Define some types', async () => { // start the type system const typir = createTypirServices({ // customize some default factories for predefined types factory: { Classes: (services) => new ClassKind(services, { - typing: "Structural", + typing: 'Structural', maximumNumberOfSuperClasses: 1, - subtypeFieldChecking: "SUB_TYPE", + subtypeFieldChecking: 'SUB_TYPE', }), }, }); // reuse predefined kinds const multiplicityKind = new MultiplicityKind(typir, { - symbolForUnlimited: "*", + symbolForUnlimited: '*', }); const listKind = new FixedParameterKind( typir, - "List", - { parameterSubtypeCheckingStrategy: "EQUAL_TYPE" }, - "entry", + 'List', + { parameterSubtypeCheckingStrategy: 'EQUAL_TYPE' }, + 'entry', ); const mapKind = new FixedParameterKind( typir, - "Map", - { parameterSubtypeCheckingStrategy: "EQUAL_TYPE" }, - "key", - "value", + 'Map', + { parameterSubtypeCheckingStrategy: 'EQUAL_TYPE' }, + 'key', + 'value', ); // create some primitive types const typeInt = typir.factory.Primitives.create({ - primitiveName: "Integer", + primitiveName: 'Integer', }).finish(); const typeString = typir.factory.Primitives.create({ - primitiveName: "String", + primitiveName: 'String', }) .inferenceRule({ - filter: (languageNode) => typeof languageNode === "string", + filter: (languageNode) => typeof languageNode === 'string', }) .finish(); // combine type definition with a dedicated inference rule for it const typeBoolean = typir.factory.Primitives.create({ - primitiveName: "Boolean", + primitiveName: 'Boolean', }).finish(); // create class type Person with 1 firstName and 1..2 lastNames and an age properties @@ -71,19 +71,19 @@ describe("Tests for Typir", () => { upperBound: 2, }); const typePerson = typir.factory.Classes.create({ - className: "Person", + className: 'Person', fields: [ - { name: "firstName", type: typeString }, - { name: "lastName", type: typeOneOrTwoStrings }, - { name: "age", type: typeInt }, + { name: 'firstName', type: typeString }, + { name: 'lastName', type: typeOneOrTwoStrings }, + { name: 'age', type: typeInt }, ], methods: [], }).finish(); console.log(typePerson.getTypeFinal()!.getUserRepresentation()); const typeStudent = typir.factory.Classes.create({ - className: "Student", + className: 'Student', superClasses: typePerson, // a Student is a special Person - fields: [{ name: "studentNumber", type: typeInt }], + fields: [{ name: 'studentNumber', type: typeInt }], methods: [], }).finish(); @@ -96,38 +96,38 @@ describe("Tests for Typir", () => { }); // const typeMapStringPerson = mapKind.createFixedParameterType({ parameterTypes: [typeString, typePerson] }); const typeFunctionStringLength = typir.factory.Functions.create({ - functionName: "length", + functionName: 'length', outputParameter: { name: NO_PARAMETER_NAME, type: typeInt }, - inputParameters: [{ name: "value", type: typeString }], + inputParameters: [{ name: 'value', type: typeString }], }).finish(); // binary operators on Integers const opAdd = typir.factory.Operators.createBinary({ - name: "+", + name: '+', signature: { left: typeInt, right: typeInt, return: typeInt }, }).finish(); const opMinus = typir.factory.Operators.createBinary({ - name: "-", + name: '-', signature: { left: typeInt, right: typeInt, return: typeInt }, }).finish(); const opLess = typir.factory.Operators.createBinary({ - name: "<", + name: '<', signature: { left: typeInt, right: typeInt, return: typeBoolean }, }).finish(); const opEqualInt = typir.factory.Operators.createBinary({ - name: "==", + name: '==', signature: { left: typeInt, right: typeInt, return: typeBoolean }, }) .inferenceRule({ filter: (languageNode): languageNode is string => - typeof languageNode === "string", - matching: (languageNode) => languageNode.includes("=="), + typeof languageNode === 'string', + matching: (languageNode) => languageNode.includes('=='), operands: (languageNode) => [], }) .finish(); // binary operators on Booleans const opEqualBool = typir.factory.Operators.createBinary({ - name: "==", + name: '==', signature: { left: typeBoolean, right: typeBoolean, @@ -135,7 +135,7 @@ describe("Tests for Typir", () => { }, }).finish(); const opAnd = typir.factory.Operators.createBinary({ - name: "&&", + name: '&&', signature: { left: typeBoolean, right: typeBoolean, @@ -144,19 +144,19 @@ describe("Tests for Typir", () => { }).finish(); // unary operators const opNotBool = typir.factory.Operators.createUnary({ - name: "!", + name: '!', signature: { operand: typeBoolean, return: typeBoolean }, }) .inferenceRule({ filter: (languageNode): languageNode is string => - typeof languageNode === "string", - matching: (languageNode) => languageNode.includes("NOT"), + typeof languageNode === 'string', + matching: (languageNode) => languageNode.includes('NOT'), operand: (languageNode) => [], }) .finish(); // ternary operator const opTernaryIf = typir.factory.Operators.createTernary({ - name: "if", + name: 'if', signature: { first: typeBoolean, second: typeInt, @@ -166,12 +166,12 @@ describe("Tests for Typir", () => { }).finish(); // automated conversion from int to string - typir.Conversion.markAsConvertible(typeInt, typeString, "EXPLICIT"); + typir.Conversion.markAsConvertible(typeInt, typeString, 'EXPLICIT'); // single relationships are possible as well typir.Conversion.markAsConvertible( typeInt, typeString, - "IMPLICIT_EXPLICIT", + 'IMPLICIT_EXPLICIT', ); // is assignable? diff --git a/packages/typir/test/utils/test-utils.test.ts b/packages/typir/test/utils/test-utils.test.ts index e477173d..07602f84 100644 --- a/packages/typir/test/utils/test-utils.test.ts +++ b/packages/typir/test/utils/test-utils.test.ts @@ -4,22 +4,22 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { beforeAll, describe, test } from "vitest"; +import { beforeAll, describe, test } from 'vitest'; +import type { TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; import { integer123, IntegerLiteral, StatementBlock, TestExpressionNode, - TestLanguageNode, -} from "../../src/test/predefined-language-nodes.js"; -import { TypirServices } from "../../src/typir.js"; +} from '../../src/test/predefined-language-nodes.js'; +import type { TypirServices } from '../../src/typir.js'; import { createTypirServicesForTesting, expectValidationIssues, expectValidationIssuesAbsent, expectValidationIssuesNone, expectValidationIssuesStrict, -} from "../../src/utils/test-utils.js"; +} from '../../src/utils/test-utils.js'; describe('Test cases for the "expectValidationIssues*(...)" test utilities', () => { let typir: TypirServices; @@ -30,100 +30,100 @@ describe('Test cases for the "expectValidationIssues*(...)" test utilities', () if (node instanceof TestExpressionNode) { accept({ languageNode: node, - severity: "error", - message: "found Expression", + severity: 'error', + message: 'found Expression', }); } if (node instanceof IntegerLiteral) { accept({ languageNode: node, - severity: "error", - message: "found Integer literal", + severity: 'error', + message: 'found Integer literal', }); } }); }); - test("some issues (some of the actual issues are expected)", () => { - expectValidationIssues(typir, integer123, ["found Integer literal"]); // "found Expression" is ignored here + test('some issues (some of the actual issues are expected)', () => { + expectValidationIssues(typir, integer123, ['found Integer literal']); // "found Expression" is ignored here }); - test("some issues (all of the actual issues are expected)", () => { + test('some issues (all of the actual issues are expected)', () => { expectValidationIssues(typir, integer123, [ - "found Integer literal", - "found Expression", + 'found Integer literal', + 'found Expression', ]); }); - test("some issues (none of the actual issues are expected)", () => { + test('some issues (none of the actual issues are expected)', () => { expectValidationIssues(typir, integer123, []); }); test.fails( - "some issues (fails, since an issue is expected, but does not occur)", + 'some issues (fails, since an issue is expected, but does not occur)', () => { expectValidationIssues(typir, integer123, [ - "found Integer literal", - "found WhatEverNode", + 'found Integer literal', + 'found WhatEverNode', ]); }, ); - test("strict (all of the actual issues are expected)", () => { + test('strict (all of the actual issues are expected)', () => { expectValidationIssuesStrict(typir, integer123, [ - "found Integer literal", - "found Expression", + 'found Integer literal', + 'found Expression', ]); }); - test("strict (all of the actual issues are expected: errors)", () => { - expectValidationIssuesStrict(typir, integer123, { severity: "error" }, [ - "found Integer literal", - "found Expression", + test('strict (all of the actual issues are expected: errors)', () => { + expectValidationIssuesStrict(typir, integer123, { severity: 'error' }, [ + 'found Integer literal', + 'found Expression', ]); }); - test("strict (all of the actual issues are expected: warnings)", () => { + test('strict (all of the actual issues are expected: warnings)', () => { expectValidationIssuesStrict( typir, integer123, - { severity: "warning" }, + { severity: 'warning' }, [], ); }); - test.fails("strict (fails: too less)", () => { + test.fails('strict (fails: too less)', () => { expectValidationIssuesStrict(typir, integer123, [ - "found Integer literal", + 'found Integer literal', ]); }); - test.fails("strict (fails: too much)", () => { + test.fails('strict (fails: too much)', () => { expectValidationIssuesStrict(typir, integer123, [ - "found Integer literal", - "found Expression", - "found WhatEverNode", + 'found Integer literal', + 'found Expression', + 'found WhatEverNode', ]); }); - test("absent (only a absent issue)", () => { - expectValidationIssuesAbsent(typir, integer123, ["found WhatEverNode"]); + test('absent (only a absent issue)', () => { + expectValidationIssuesAbsent(typir, integer123, ['found WhatEverNode']); }); - test.fails("absent (fails, since the given issue occurs)", () => { - expectValidationIssuesAbsent(typir, integer123, ["found Expression"]); + test.fails('absent (fails, since the given issue occurs)', () => { + expectValidationIssuesAbsent(typir, integer123, ['found Expression']); }); - test("absent (the specified issue occurs as error, not as warning)", () => { + test('absent (the specified issue occurs as error, not as warning)', () => { expectValidationIssuesAbsent( typir, integer123, - { severity: "warning" }, - ["found Expression"], + { severity: 'warning' }, + ['found Expression'], ); }); - test("absent (works even for an empty array)", () => { + test('absent (works even for an empty array)', () => { expectValidationIssuesAbsent(typir, integer123, []); }); - test("none (at all)", () => { + test('none (at all)', () => { expectValidationIssuesNone(typir, new StatementBlock([])); }); - test("none warnings", () => { - expectValidationIssuesNone(typir, integer123, { severity: "warning" }); + test('none warnings', () => { + expectValidationIssuesNone(typir, integer123, { severity: 'warning' }); }); - test.fails("none errors fails, since there are error issues", () => { - expectValidationIssuesNone(typir, integer123, { severity: "error" }); + test.fails('none errors fails, since there are error issues', () => { + expectValidationIssuesNone(typir, integer123, { severity: 'error' }); }); }); diff --git a/vite.config.ts b/vite.config.ts index 1a68847c..c94c864f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,15 +4,15 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { defineConfig } from "vitest/config"; +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { coverage: { - provider: "c8", - reporter: ["text", "html"], - include: ["packages/typir/src"], - exclude: ["**/generated"], + provider: 'c8', + reporter: ['text', 'html'], + include: ['packages/typir/src'], + exclude: ['**/generated'], }, deps: { interopDefault: true,