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/.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..739446ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: ["main"] + tags-ignore: + - "**" + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + format-and-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: + 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 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..d7909d31 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,299 @@ +/*[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); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + 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/**', + '**/out/**/*', + '**/language/generated/**/*', + ], + }, + { + 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', + ), + + plugins: { + '@typescript-eslint': pluginTypescriptEslint, + import: pluginImport, + 'unused-imports': pluginUnusedImports, + pluginHeader, + '@stylistic': pluginStylistic, + }, + + languageOptions: { + globals: { + ...globals.node, + ...globals.browser, + }, + 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", + + // 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, + 'single', + { + avoidEscape: true, + }, + ], + // 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', + { + 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', + + // 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', + { + 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', + + // 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 e273640d..af12988f 100644 --- a/examples/lox/src/cli/cli-util.ts +++ b/examples/lox/src/cli/cli-util.ts @@ -9,12 +9,19 @@ 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 { LangiumServices } from 'langium/lsp'; -export async function extractDocument(fileName: string, services: LangiumCoreServices): Promise { +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:')); 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..e6f0544a 100644 --- a/examples/lox/src/cli/main.ts +++ b/examples/lox/src/cli/main.ts @@ -17,7 +17,10 @@ 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 => { +export const generateAction = async ( + fileName: string, + opts: GenerateOptions, +): Promise => { // const services = createOxServices(NodeFileSystem).Ox; // const program = await extractAstNode(fileName, services); @@ -29,7 +32,7 @@ export const generateAction = async (fileName: string, opts: GenerateOptions): P export type GenerateOptions = { destination?: string; -} +}; export default function(): void { const program = new Command(); @@ -39,8 +42,14 @@ export default function(): void { const fileExtensions = LoxLanguageMetaData.fileExtensions.join(', '); program .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') + .argument( + '', + `source file (possible file extensions: ${fileExtensions})`, + ) + .option( + '-d, --destination ', + 'destination directory of generating', + ) .description('generates from the source file') .action(generateAction); diff --git a/examples/lox/src/extension/main.ts b/examples/lox/src/extension/main.ts index a153057a..eb8d5db9 100644 --- a/examples/lox/src/extension/main.ts +++ b/examples/lox/src/extension/main.ts @@ -4,7 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { LanguageClientOptions, ServerOptions} 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'; @@ -25,20 +28,32 @@ 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 @@ -46,8 +61,8 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { 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. @@ -55,7 +70,7 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { '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..43a6c05b 100644 --- a/examples/lox/src/language/lox-linker.ts +++ b/examples/lox/src/language/lox-linker.ts @@ -4,11 +4,18 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNodeDescription, DefaultLinker, LinkingError, ReferenceInfo } from 'langium'; +import type { AstNodeDescription, LinkingError, ReferenceInfo } from 'langium'; +import { DefaultLinker } 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 type { TypirLangiumServices } from 'typir-langium'; +import type { LoxAstType } from './generated/ast.js'; +import { + isClass, + isFunctionDeclaration, + isMemberCall, + isMethodMember, +} from './generated/ast.js'; +import type { LoxServices } from './lox-module.js'; export class LoxLinker extends DefaultLinker { protected readonly typir: TypirLangiumServices; @@ -18,29 +25,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..e9e900bc 100644 --- a/examples/lox/src/language/lox-module.ts +++ b/examples/lox/src/language/lox-module.ts @@ -4,11 +4,29 @@ * 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 type { + LangiumSharedCoreServices, + Module, + PartialLangiumCoreServices, +} from 'langium'; +import { createDefaultCoreModule, inject } from 'langium'; +import type { + DefaultSharedModuleContext, + LangiumServices, + LangiumSharedServices, +} from 'langium/lsp'; +import { createDefaultSharedModule } from 'langium/lsp'; +import type { TypirLangiumServices } from 'typir-langium'; +import { + createTypirLangiumServices, + initializeLangiumTypirServices, +} 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'; @@ -19,30 +37,41 @@ import { LoxValidationRegistry, LoxValidator } from './lox-validator.js'; */ 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..a0cbcbb3 100644 --- a/examples/lox/src/language/lox-type-checking.ts +++ b/examples/lox/src/language/lox-type-checking.ts @@ -2,108 +2,253 @@ * 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 type { AstNode } from 'langium'; +import { AstUtils, assertUnreachable } from 'langium'; +import type { + CreateFieldDetails, + CreateMethodDetails, + CreateParameterDetails, + FunctionType, + InferOperatorWithMultipleOperands, + InferOperatorWithSingleOperand, + TypeInitializer, + TypirServices, + ValidationProblemAcceptor, +} from 'typir'; +import { InferenceRuleNotApplicable, NO_PARAMETER_NAME } from 'typir'; +import type { + TypirLangiumServices, + LangiumTypeSystemDefinition, +} from 'typir-langium'; +import type { + ForStatement, + IfStatement, + LoxAstType, + VariableDeclaration, + WhileStatement, +} from './generated/ast.js'; +import { + BinaryExpression, + BooleanLiteral, + Class, + FunctionDeclaration, + MemberCall, + MethodMember, + NilLiteral, + NumberLiteral, + PrintStatement, + ReturnStatement, + StringLiteral, + TypeReference, + UnaryExpression, + 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(); + 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(); + 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(); + 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 }}) + 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 +297,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 +313,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 +335,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 +436,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 +461,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..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 d34d41cf..ade33b09 100644 --- a/examples/lox/src/language/lox-validator.ts +++ b/examples/lox/src/language/lox-validator.ts @@ -4,8 +4,9 @@ * 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 { ValidationAcceptor, ValidationChecks } from 'langium'; +import { ValidationRegistry } from 'langium'; +import type { LoxAstType, VariableDeclaration } from './generated/ast.js'; import type { LoxServices } from './lox-module.js'; /** @@ -27,14 +28,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..27260a13 100644 --- a/examples/lox/src/language/main.ts +++ b/examples/lox/src/language/main.ts @@ -6,7 +6,10 @@ import { startLanguageServer } from 'langium/lsp'; import { NodeFileSystem } from 'langium/node'; -import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js'; +import { + createConnection, + ProposedFeatures, +} from 'vscode-languageserver/node.js'; import { createLoxServices } from './lox-module.js'; // Create a connection to the client diff --git a/examples/lox/syntaxes/lox.monarch.ts b/examples/lox/syntaxes/lox.monarch.ts index 71aefc03..b241533b 100644 --- a/examples/lox/syntaxes/lox.monarch.ts +++ b/examples/lox/syntaxes/lox.monarch.ts @@ -1,30 +1,87 @@ +/****************************************************************************** + * 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: /!|!=|\(|\)|\*|\+|,|-|\.|\/|:|;|<|<=|=|==|=>|>|>=|\{|\}/, 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"} }, + { + 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: /@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..7f82804f 100644 --- a/examples/lox/test/lox-type-checking-classes.test.ts +++ b/examples/lox/test/lox-type-checking-classes.test.ts @@ -8,131 +8,185 @@ 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 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 () => { - await validateLox(` + 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(` + 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(` + await validateLox( + ` class MyClass1 { name: string age: number } var v1: MyClass1 = MyClass1(); v1.name = "Bob"; v1.age = 42; - `, 0); + `, + 0, + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class fields: wrong values', async () => { - await validateLox(` + await validateLox( + ` class MyClass1 { name: string age: number } var v1: MyClass1 = MyClass1(); v1.name = 42; v1.age = "Bob"; - `, 2); + `, + 2, + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Classes must be unique by name 2', async () => { - await validateLox(` + 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'); }); test('Classes must be unique by name 3', async () => { - await validateLox(` + 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).', - ]); + `, + [ + '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(` + await validateLox( + ` class MyClass { name: string age: number } var v1 = MyClass(); // constructor call - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); test('Class literals 2', async () => { - await validateLox(` + await validateLox( + ` class MyClass { name: string age: number } var v1: MyClass = MyClass(); // constructor call - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); test('Class literals 3', async () => { - await validateLox(` + 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(` + 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(` + 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; + `, + [], + ) + ).parseResult.value as LoxProgram; checkVariableDeclaration(program, 'v2', 'string'); checkVariableDeclaration(program, 'v3', 'number'); }); test('different classes with switched properties', async () => { - const program = (await validateLox(` + const program = ( + await validateLox( + ` class MyClass1 { name: string age: number } class MyClass2 { name: number age: string } var v1: MyClass1 = MyClass1(); @@ -141,16 +195,30 @@ describe('Class field access', () => { var v1age = v1.age; var v2name = v2.name; var v2age = v2.age; - `, [])).parseResult.value as LoxProgram; + `, + [], + ) + ).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..0c376fba 100644 --- a/examples/lox/test/lox-type-checking-cycles.test.ts +++ b/examples/lox/test/lox-type-checking-cycles.test.ts @@ -7,32 +7,43 @@ 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'; +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(` + await validateLox( + ` class Node { children: Node } - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'Node'); }); test('Two Classes with fields with the other Class as type', async () => { - await validateLox(` + await validateLox( + ` class A { prop1: B } class B { prop2: A } - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); test('Three Classes with fields with one of the other Classes as type', async () => { - await validateLox(` + 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'); }); test('Three Classes with fields with two of the other Classes as type', async () => { - await validateLox(` + 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'); }); test('Class with field of its own type and another dependency', async () => { - await validateLox(` + 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'); }); test('Two Classes with a field of its own type and cyclic dependencies to each other', async () => { - await validateLox(` + 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'); }); test('Having two declarations for the delayed class A, but only one type A in the type system', async () => { - await validateLox(` + 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'); }); test('Having three declarations for the delayed class A, but only one type A in the type system', async () => { - await validateLox(` + 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'); }); test('Having two declarations for class A waiting for B, while B itself depends on A', async () => { - await validateLox(` + 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'); }); test('Class with method: cycle with return type', async () => { - await validateLox(` + await validateLox( + ` class Node { myMethod(input: number): Node {} } - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'Node'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes( + loxServices.typir, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Class with method: cycle with input parameter type', async () => { - await validateLox(` + await validateLox( + ` class Node { myMethod(input: Node): number {} } - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'Node'); - expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); + 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(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Two different Classes depend on each other regarding their methods return type', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + 'myMethod', + ...operatorNames, + ); }); test('Two different Classes with the same method which has one of these classes as return type', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Same delayed function type is used by a function declaration and a method declaration', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Two class declarations A with the same delayed method which depends on the class B', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Mix of dependencies in classes: 1 method and 1 field', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Mix of dependencies in classes: 1 method and 2 fields (order 1)', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('Mix of dependencies in classes: 1 method and 2 fields (order 2)', async () => { - await validateLox(` + 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, + isFunctionType, + 'myMethod', + ...operatorNames, + ); }); test('The same class is involved into two dependency cycles', async () => { - await validateLox(` + await validateLox( + ` class A { probA: C1 myMethod(input: number): B1 {} @@ -306,59 +423,112 @@ 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 () => { // 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 () => { // 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(` + 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(` + 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(` + 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'); }); }); @@ -366,7 +536,8 @@ describe('Test internal validation of Typir for cycles in the class inheritance 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(` + 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(` + 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..cbdf3f53 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 { + 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 type { LoxProgram } from '../src/language/generated/ast.js'; +import { + isFunctionDeclaration, + isMemberCall, +} 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 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); + 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(` + 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(` + 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(` + 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(` + 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); @@ -90,5 +152,4 @@ describe('Test type checking for user-defined functions', () => { assertTypirType(call2Type, isPrimitiveType); 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..aa49076f 100644 --- a/examples/lox/test/lox-type-checking-method.test.ts +++ b/examples/lox/test/lox-type-checking-method.test.ts @@ -4,16 +4,28 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { assertTrue, assertTypirType, isClassType, isFunctionType, isPrimitiveType, isType } from 'typir'; +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 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'; describe('Test type checking for methods of classes', () => { - test('Class methods: OK', async () => { - await validateLox(` + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -21,12 +33,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); - `, []); + `, + [], + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: wrong return value', async () => { - await validateLox(` + await validateLox( + ` class MyClass1 { method1(input: number): number { return true; @@ -34,12 +49,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); - `, 1); + `, + 1, + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: method return type does not fit to variable type', async () => { - await validateLox(` + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -47,12 +65,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: boolean = v1.method1(456); - `, 1); + `, + 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(` + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -60,12 +81,15 @@ describe('Test type checking for methods of classes', () => { } var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(true); - `, 1); + `, + 1, + ); expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: methods are not distinguishable', async () => { - await validateLox(` + await validateLox( + ` class MyClass1 { method1(input: number): number { return 123; @@ -74,13 +98,15 @@ 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)).', - ]); + `, + [ + // 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', () => { @@ -96,13 +122,24 @@ describe('Test overloaded methods', () => { `; test('Calls with correct arguments', async () => { - const rootNode = (await validateLox(`${methodDeclaration} + const rootNode = ( + await validateLox( + `${methodDeclaration} var v = MyClass(); v.method1(123); v.method1(false); - `, [])).parseResult.value as LoxProgram; + `, + [], + ) + ).parseResult.value as LoxProgram; expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); - expectTypirTypes(loxServices.typir, isFunctionType, 'method1', 'method1', ...operatorNames); + expectTypirTypes( + loxServices.typir, + isFunctionType, + 'method1', + 'method1', + ...operatorNames, + ); // check type inference + cross-reference of the two method calls expect(rootNode.elements).toHaveLength(4); @@ -135,12 +172,14 @@ describe('Test overloaded methods', () => { }); test('Call with wrong argument', async () => { - await validateLox(`${methodDeclaration} + 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..627c122a 100644 --- a/examples/lox/test/lox-type-checking-operators.test.ts +++ b/examples/lox/test/lox-type-checking-operators.test.ts @@ -8,7 +8,6 @@ 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); @@ -28,10 +27,16 @@ describe('Test type checking for operators', () => { 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 () => { @@ -83,5 +88,4 @@ describe('Test type checking for operators', () => { "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..0aa6fd56 100644 --- a/examples/lox/test/lox-type-checking-statements.test.ts +++ b/examples/lox/test/lox-type-checking-statements.test.ts @@ -8,10 +8,12 @@ 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); + await validateLox( + 'var myResult: boolean = true and false and true;', + 0, + ); }); test('number assignments', async () => { @@ -47,59 +49,79 @@ describe('Test type checking for statements and variables in LOX', () => { }); test('Variables without explicit type: assignment', async () => { - await validateLox(` + 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(` + 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(` + 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(` + 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(` + 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(` + await validateLox( + ` var min = 14; var max = 22; max = min * max; - `, 0); + `, + 0, + ); }); test('Variables without explicit type: used in function', async () => { - await validateLox(` + 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..5f42862d 100644 --- a/examples/lox/test/lox-type-checking-utils.ts +++ b/examples/lox/test/lox-type-checking-utils.ts @@ -4,7 +4,8 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { EmptyFileSystem, LangiumDocument } from 'langium'; +import type { LangiumDocument } from 'langium'; +import { EmptyFileSystem } from 'langium'; import { parseDocument } from 'langium/test'; import { isClassType, isFunctionType } from 'typir'; import { deleteAllDocuments } from 'typir-langium'; @@ -15,7 +16,26 @@ 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,22 +44,36 @@ 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 { +function checkIssues( + diagnosticsErrors: string[], + errors: number | string | string[], +): void { const msgError = diagnosticsErrors.join('\n'); if (typeof errors === 'number') { expect(diagnosticsErrors, msgError).toHaveLength(errors); diff --git a/examples/ox/src/cli/cli-util.ts b/examples/ox/src/cli/cli-util.ts index 2c2370cd..f8c581e2 100644 --- a/examples/ox/src/cli/cli-util.ts +++ b/examples/ox/src/cli/cli-util.ts @@ -9,12 +9,19 @@ 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 { LangiumServices } from 'langium/lsp'; -export async function extractDocument(fileName: string, services: LangiumCoreServices): Promise { +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:')); 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..79c29eac 100644 --- a/examples/ox/src/cli/main.ts +++ b/examples/ox/src/cli/main.ts @@ -17,7 +17,10 @@ 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 => { +export const generateAction = async ( + fileName: string, + opts: GenerateOptions, +): Promise => { // const services = createOxServices(NodeFileSystem).Ox; // const program = await extractAstNode(fileName, services); @@ -29,7 +32,7 @@ export const generateAction = async (fileName: string, opts: GenerateOptions): P export type GenerateOptions = { destination?: string; -} +}; export default function(): void { const program = new Command(); @@ -39,8 +42,14 @@ export default function(): void { const fileExtensions = OxLanguageMetaData.fileExtensions.join(', '); program .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') + .argument( + '', + `source file (possible file extensions: ${fileExtensions})`, + ) + .option( + '-d, --destination ', + 'destination directory of generating', + ) .description('generates from the source file') .action(generateAction); diff --git a/examples/ox/src/extension/main.ts b/examples/ox/src/extension/main.ts index f661e671..b990b881 100644 --- a/examples/ox/src/extension/main.ts +++ b/examples/ox/src/extension/main.ts @@ -4,7 +4,10 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import type { LanguageClientOptions, ServerOptions} 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'; @@ -25,20 +28,32 @@ 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 @@ -46,17 +61,12 @@ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient { 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..c247692b 100644 --- a/examples/ox/src/language/main.ts +++ b/examples/ox/src/language/main.ts @@ -6,7 +6,10 @@ import { startLanguageServer } from 'langium/lsp'; import { NodeFileSystem } from 'langium/node'; -import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js'; +import { + createConnection, + ProposedFeatures, +} from 'vscode-languageserver/node.js'; import { createOxServices } from './ox-module.js'; // Create a connection to the client diff --git a/examples/ox/src/language/ox-module.ts b/examples/ox/src/language/ox-module.ts index 1516714b..723982db 100644 --- a/examples/ox/src/language/ox-module.ts +++ b/examples/ox/src/language/ox-module.ts @@ -4,11 +4,26 @@ * 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 type { LangiumSharedCoreServices, Module } from 'langium'; +import { inject } from 'langium'; +import type { + DefaultSharedModuleContext, + LangiumServices, + LangiumSharedServices, + PartialLangiumServices, +} from 'langium/lsp'; +import { createDefaultModule, createDefaultSharedModule } from 'langium/lsp'; +import type { TypirLangiumServices } from 'typir-langium'; +import { + createTypirLangiumServices, + initializeLangiumTypirServices, +} 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'; @@ -17,29 +32,34 @@ import { OxValidator, registerValidationChecks } from './ox-validator.js'; */ 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 +79,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, }; @@ -44,27 +100,69 @@ export class OxTypeSystem implements LangiumTypeSystemDefinition { // 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(); + 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(); + 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(); + 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(); + 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 +213,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', - })); + }), + ); } }, 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 +300,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 +325,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 +336,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..3232d069 100644 --- a/examples/ox/src/language/ox-validator.ts +++ b/examples/ox/src/language/ox-validator.ts @@ -4,8 +4,24 @@ * 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 { + AstUtils, + MultiMap, + type ValidationAcceptor, + type ValidationChecks, +} from 'langium'; +import type { + FunctionDeclaration, + 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'; /** @@ -30,14 +46,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') { // 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 +72,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 +101,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..7cfdf9f4 100644 --- a/examples/ox/test/ox-type-checking-functions.test.ts +++ b/examples/ox/test/ox-type-checking-functions.test.ts @@ -8,7 +8,6 @@ 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); @@ -17,19 +16,24 @@ describe('Test type checking for statements and variables in OX', () => { }); test('function: the same function name twice (in the same file) is not allowed in Typir', async () => { - await validateOx(` + 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..9fa2b6ff 100644 --- a/examples/ox/test/ox-type-checking-operators.test.ts +++ b/examples/ox/test/ox-type-checking-operators.test.ts @@ -8,7 +8,6 @@ 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); @@ -65,5 +64,4 @@ describe('Test type checking for statements and variables in OX', () => { "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..91b9c66b 100644 --- a/examples/ox/test/ox-type-checking-statements.test.ts +++ b/examples/ox/test/ox-type-checking-statements.test.ts @@ -8,7 +8,6 @@ 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); @@ -45,5 +44,4 @@ describe('Test type checking for statements and variables in OX', () => { 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..102aef52 100644 --- a/examples/ox/test/ox-type-checking-utils.ts +++ b/examples/ox/test/ox-type-checking-utils.ts @@ -13,7 +13,24 @@ 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,9 +38,14 @@ 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 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); diff --git a/package-lock.json b/package-lock.json index 2e551ae5..75feab9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,15 +15,17 @@ "examples/lox" ], "devDependencies": { + "@stylistic/eslint-plugin": "^4.4.0", "@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", "esbuild": "^0.25.0", - "eslint": "~8.57.1", + "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", "semver": "^7.7.1", "shx": "~0.3.4", @@ -546,51 +548,169 @@ } }, "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" } }, "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 +726,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", @@ -624,6 +751,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" @@ -637,6 +765,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" } @@ -646,6 +775,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" @@ -874,12 +1004,79 @@ "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", "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/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", @@ -896,117 +1093,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/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": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "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/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", + "@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" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "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": { + "@typescript-eslint/tsconfig-utils": "^8.33.0", + "@typescript-eslint/types": "^8.33.0", + "debug": "^4.3.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "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==", + "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/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@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" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, "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==", + "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/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/utils": "8.33.0", "debug": "^4.3.4", - "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": { - "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/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", @@ -1014,31 +1251,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": { @@ -1046,6 +1284,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" } @@ -1055,6 +1294,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" }, @@ -1066,49 +1306,59 @@ } }, "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==", + "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.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@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.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" + "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/@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/@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", @@ -1239,11 +1489,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 +1506,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 +1516,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,73 +1552,269 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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": { + "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "engines": { - "node": ">=12" - } + "license": "Python-2.0" }, - "node_modules/balanced-match": { + "node_modules/array-buffer-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "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": { - "fill-range": "^7.1.1" + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "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": ">=8" + "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", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "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": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "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", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "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", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "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", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", @@ -1553,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", @@ -1585,28 +2087,68 @@ "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==", + "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": { - "path-type": "^4.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "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": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "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": ">=6.0.0" + "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": { @@ -1657,121 +2199,322 @@ "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", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "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": ">=6" + "node": ">= 0.4" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "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": ">=10" + "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", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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-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": { @@ -1783,84 +2526,98 @@ "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-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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=4" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "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-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": { - "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" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "ms": "^2.1.1" } }, - "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==", + "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": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "semver": "bin/semver.js" } }, - "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-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/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "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==", + "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": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1868,30 +2625,33 @@ "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "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, + "license": "Apache-2.0", "engines": { - "node": ">=4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/glob-parent": { @@ -1906,16 +2666,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 +2709,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 +2722,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 +2746,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,19 +2755,21 @@ "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", - "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" @@ -2004,7 +2779,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", @@ -2013,10 +2789,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" } @@ -2028,15 +2805,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": { @@ -2044,6 +2822,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" }, @@ -2068,17 +2847,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": { @@ -2087,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", @@ -2130,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", @@ -2139,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", @@ -2165,6 +3048,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" }, @@ -2173,38 +3057,46 @@ } }, "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" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "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": { - "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" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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": { @@ -2217,7 +3109,21 @@ "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-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", @@ -2228,6 +3134,64 @@ "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", @@ -2250,10 +3214,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" @@ -2285,80 +3250,427 @@ "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/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "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/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": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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==", + "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, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -2366,17 +3678,32 @@ "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", @@ -2384,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", @@ -2410,6 +3750,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" } @@ -2534,11 +3875,22 @@ "@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", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -2548,6 +3900,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" @@ -2616,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", @@ -2642,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", @@ -2677,6 +4145,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" }, @@ -2717,15 +4186,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", @@ -2752,6 +4212,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2759,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", @@ -2801,6 +4272,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2823,7 +4295,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/railroad-diagrams": { "version": "1.0.0", @@ -2843,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", @@ -2874,36 +4391,22 @@ "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" } }, "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" } }, - "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", @@ -2958,17 +4461,73 @@ "url": "https://feross.org/support" } ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "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": { - "queue-microtask": "^1.2.2" + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "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": { - "tslib": "^2.1.0" + "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": { @@ -2982,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", @@ -3045,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", @@ -3065,15 +4749,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", @@ -3109,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", @@ -3121,11 +4855,22 @@ "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", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -3157,12 +4902,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", @@ -3246,6 +4985,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" }, @@ -3272,15 +5012,29 @@ } }, "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/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": { @@ -3301,16 +5055,82 @@ "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==", + "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": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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": { @@ -3342,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", @@ -3362,6 +5201,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" } @@ -4006,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 1690a1ae..8f474d1f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "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 .", + "lint:fix": "eslint . --fix", "test": "vitest", "test:run": "vitest --run", "test-ui": "vitest --ui", @@ -30,15 +31,17 @@ "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": "~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", "esbuild": "^0.25.0", - "eslint": "~8.57.1", + "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", "semver": "^7.7.1", "shx": "~0.3.4", diff --git a/packages/typir-langium/src/features/langium-caching.ts b/packages/typir-langium/src/features/langium-caching.ts index f2742bdc..c5940a80 100644 --- a/packages/typir-langium/src/features/langium-caching.ts +++ b/packages/typir-langium/src/features/langium-caching.ts @@ -4,16 +4,23 @@ * 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 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 { +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 +32,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 +44,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 +62,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..408a6e75 100644 --- a/packages/typir-langium/src/features/langium-inference.ts +++ b/packages/typir-langium/src/features/langium-inference.ts @@ -4,27 +4,45 @@ * 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 ? 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 callbacks = ruleCallbacks as + | TypeInferenceRule + | Array>; if (Array.isArray(callbacks)) { for (const callback of callbacks) { this.addInferenceRule(callback, { languageKey }); @@ -34,5 +52,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 +57,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..69cfe2c8 100644 --- a/packages/typir-langium/src/features/langium-printing.ts +++ b/packages/typir-langium/src/features/langium-printing.ts @@ -4,17 +4,19 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, isAstNode } from 'langium'; +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. */ - 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 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..297d5e49 100644 --- a/packages/typir-langium/src/features/langium-type-creator.ts +++ b/packages/typir-langium/src/features/langium-type-creator.ts @@ -4,10 +4,19 @@ * 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 type { + AstNode, + LangiumDocument, + LangiumSharedCoreServices, +} 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, +} from '../utils/typir-langium-utils.js'; /** * This service provides the API to define the actual types, inference rules and validation rules @@ -28,10 +37,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,7 +55,9 @@ 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 readonly documentTypesMap: Map = new Map(); @@ -52,29 +65,37 @@ export class DefaultLangiumTypeCreator impleme 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 +122,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 } 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 +161,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..a46c019b 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 type { + AstNode, + LangiumDefaultCoreServices, + ValidationAcceptor, + ValidationChecks, +} from 'langium'; +import type { + TypirServices, + ValidationCollector, + ValidationProblem, + ValidationRule, +} 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, + 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 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/typir-langium.ts b/packages/typir-langium/src/typir-langium.ts index 370611f1..6ea4352a 100644 --- a/packages/typir-langium/src/typir-langium.ts +++ b/packages/typir-langium/src/typir-langium.ts @@ -4,15 +4,39 @@ * 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 type { + AbstractAstReflection, + AstNode, + LangiumDefaultCoreServices, + LangiumSharedCoreServices, +} from 'langium'; +import type { + DeepPartial, + Module, + PartialTypirServices, + TypirServices, +} from 'typir'; +import { createDefaultTypirServicesModule, inject } from 'typir'; import { LangiumLanguageNodeInferenceCaching } from './features/langium-caching.js'; -import { DefaultLangiumTypeInferenceCollector, LangiumTypeInferenceCollector } from './features/langium-inference.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 { 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 type { + LangiumTypeCreator, + LangiumTypeSystemDefinition, +} 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, + registerTypirValidationChecks, +} from './features/langium-validation.js'; +import type { LangiumAstTypes } from './utils/typir-langium-utils.js'; /** * Additional Typir-Langium services to manage the Typir services @@ -20,7 +44,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 +53,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 +88,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 +123,9 @@ export function createDefaultTypirLangiumServicesModule( - langiumServices: LangiumSharedCoreServices, reflection: AbstractAstReflection, typeSystemDefinition: LangiumTypeSystemDefinition, + langiumServices: LangiumSharedCoreServices, + reflection: AbstractAstReflection, + typeSystemDefinition: LangiumTypeSystemDefinition, customization1: Module> = {}, customization2: Module> = {}, customization3: Module> = {}, @@ -106,8 +151,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..da783241 100644 --- a/packages/typir-langium/src/utils/typir-langium-utils.ts +++ b/packages/typir-langium/src/utils/typir-langium-utils.ts @@ -4,7 +4,13 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, LangiumDocument, LangiumSharedCoreServices, URI } from 'langium'; +import type { + AstNode, + LangiumDocument, + LangiumSharedCoreServices, + URI, +} from 'langium'; +import { AstUtils } from 'langium'; import { assertTrue } from 'typir'; export function getDocumentKeyForURI(document: URI): string { @@ -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..f829944b 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 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. * 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..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). @@ -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..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 type { 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 { TypeEdge } from './type-edge.js'; +import type { 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[] = []; @@ -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..72fac268 100644 --- a/packages/typir/src/graph/type-node.ts +++ b/packages/typir/src/graph/type-node.ts @@ -4,12 +4,20 @@ * 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 type { TypeReference } from '../initialization/type-reference.js'; +import { + WaitingForIdentifiableAndCompletedTypeReferences, + WaitingForInvalidTypeReferences, +} 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 type { TypeEdge } from './type-edge.js'; /** * The transitions between the states of a type are depicted as state machine: @@ -72,12 +80,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,8 +116,6 @@ export abstract class Type { */ abstract getUserRepresentation(): string; - - // store the state of the initialization process of this type protected initializationState: TypeInitializationState = 'Invalid'; @@ -118,17 +126,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.`, + ); } } @@ -151,12 +165,14 @@ export abstract class Type { } } - // 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(); @@ -202,18 +218,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 +237,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 +259,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,15 +301,23 @@ 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(); @@ -295,21 +327,27 @@ export abstract class Type { 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.stateListeners + .slice() + .forEach((listener) => listener.onSwitchedToIdentifiable(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromIdentifiableToCompleted(): void { 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.stateListeners + .slice() + .forEach((listener) => listener.onSwitchedToCompleted(this)); // slice() prevents issues with removal of listeners during notifications } protected switchFromCompleteOrIdentifiableToInvalid(): void { 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.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 +356,6 @@ export abstract class Type { } } - - /** * Analyzes, whether two types are equal. * @param otherType to be compared with the current type @@ -328,7 +364,6 @@ export abstract class Type { */ abstract analyzeTypeEqualityProblems(otherType: Type): TypirProblem[]; - addIncomingEdge(edge: TypeEdge): void { const key = edge.$relation; if (this.edgesIncoming.has(key)) { @@ -378,10 +413,10 @@ export abstract class Type { } getIncomingEdges($relation: T['$relation']): T[] { - return this.edgesIncoming.get($relation) as T[] ?? []; + return (this.edgesIncoming.get($relation) as T[]) ?? []; } getOutgoingEdges($relation: T['$relation']): T[] { - return this.edgesOutgoing.get($relation) as T[] ?? []; + return (this.edgesOutgoing.get($relation) as T[]) ?? []; } getEdges($relation: T['$relation']): T[] { return [ @@ -397,18 +432,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/initialization/type-initializer.ts b/packages/typir/src/initialization/type-initializer.ts index e14e3dd1..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; @@ -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..cb01f934 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 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 type { TypirServices } from '../typir.js'; import { removeFromArray } from '../utils/utils.js'; -import { TypeSelector } from './type-selector.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. @@ -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,21 +109,31 @@ 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'; } // 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)); + this.listeners + .slice() + .forEach((listener) => + listener.onTypeReferenceResolved(this, resolvedType), + ); return 'SUCCESSFULLY_RESOLVED'; } else { // the type is not resolved (yet) @@ -114,7 +141,10 @@ export class TypeReference implements Ty } } - 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..7ada2a4b 100644 --- a/packages/typir/src/initialization/type-selector.ts +++ b/packages/typir/src/initialization/type-selector.ts @@ -4,28 +4,26 @@ * 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 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 = - | 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 +33,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,14 +47,18 @@ 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; @@ -66,8 +70,13 @@ export class DefaultTypeResolver implements TypeResolvingService((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)) { @@ -83,27 +92,44 @@ export class DefaultTypeResolver implements TypeResolvingService( - 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.'); + 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..3abb34a2 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 type { 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 { 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')) { // 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')) { // 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..62fe6970 100644 --- a/packages/typir/src/kinds/bottom/bottom-kind.ts +++ b/packages/typir/src/kinds/bottom/bottom-kind.ts @@ -4,17 +4,21 @@ * 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 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 { isKind, Kind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; import { BottomType } from './bottom-type.js'; -export interface BottomTypeDetails extends TypeDetails { +export interface BottomTypeDetails + extends TypeDetails { // empty } -export interface CreateBottomTypeDetails extends BottomTypeDetails { +export interface CreateBottomTypeDetails + extends BottomTypeDetails { inferenceRules: Array>; } @@ -25,33 +29,44 @@ export interface BottomKindOptions { 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 { +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', // the actually overriden values: - ...options + ...options, }; } @@ -60,28 +75,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 +117,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..752a6c64 100644 --- a/packages/typir/src/kinds/bottom/bottom-type.ts +++ b/packages/typir/src/kinds/bottom/bottom-type.ts @@ -4,24 +4,29 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; +import type { 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 type { TypirProblem } from '../../utils/utils-definitions.js'; import { createKindConflict } from '../../utils/utils-type-comparison.js'; -import { BottomKind, BottomTypeDetails, isBottomKind } from './bottom-kind.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; - 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 +36,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 +58,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..7bdac71f 100644 --- a/packages/typir/src/kinds/class/class-initializer.ts +++ b/packages/typir/src/kinds/class/class-initializer.ts @@ -4,39 +4,88 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { isType, Type, TypeStateListener } from '../../graph/type-node.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 { 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 type { TypeInferenceRule } from '../../services/inference.js'; +import { + InferenceProblem, + InferenceRuleNotApplicable, +} from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import type { + InferenceRuleWithOptions, + ValidationRuleWithOptions, +} from '../../utils/utils-definitions.js'; +import { + bindInferCurrentTypeRule, + bindValidateCurrentTypeRule, + optionsBoundToType, +} 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 type { + 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); + 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! } @@ -57,29 +106,81 @@ export class ClassTypeInitializer extends TypeInitializer 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 +190,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,13 +251,24 @@ 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') { @@ -161,28 +294,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 +344,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 +378,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 @@ -250,17 +415,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 +444,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 +476,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 +486,4 @@ export class ClassTypeInitializer extends TypeInitializer { 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 +83,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 +148,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 { +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,7 +166,9 @@ 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', @@ -121,7 +176,7 @@ export class ClassKind implements Kind, ClassFactoryService implements Kind, ClassFactoryService | string): TypeReference { // string for nominal typing + 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 +211,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 + '-' + : ''; } /** @@ -173,18 +245,28 @@ export class ClassKind implements Kind, ClassFactoryService `${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(','); // 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(','); // 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(); }) @@ -207,59 +289,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') { // 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); + 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') { // 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 +382,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..e35e199b 100644 --- a/packages/typir/src/kinds/class/class-type.ts +++ b/packages/typir/src/kinds/class/class-type.ts @@ -7,11 +7,22 @@ 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 type { 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 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; @@ -38,11 +49,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 +66,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 +161,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.`, + ); } } }, @@ -153,7 +201,10 @@ export class ClassType extends Type { } // 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(', ')} }`; } @@ -162,35 +213,59 @@ export class ClassType extends Type { if (isClassType(otherType)) { 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)); + 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[] { + 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,7 +284,7 @@ export class ClassType extends Type { expected: superFieldType, actual: undefined, propertyName: superFieldName, - subProblems: [] + subProblems: [], }); } } @@ -220,7 +295,11 @@ export class ClassType extends Type { 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,7 +312,7 @@ 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; @@ -298,7 +377,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,13 +390,15 @@ 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); @@ -328,7 +411,7 @@ 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; @@ -347,7 +430,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..8db8632e 100644 --- a/packages/typir/src/kinds/class/class-validation.ts +++ b/packages/typir/src/kinds/class/class-validation.ts @@ -4,32 +4,55 @@ * 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 type { + ValidationProblemAcceptor, + ValidationRule, + ValidationRuleLifecycle, +} 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 { - 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,7 +79,11 @@ 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) { @@ -73,7 +100,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 +112,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,14 +209,22 @@ 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({ @@ -166,7 +240,6 @@ export class UniqueMethodValidation { isRelevant?: (languageNode: LanguageType) => boolean; } @@ -176,11 +249,24 @@ 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({ diff --git a/packages/typir/src/kinds/class/top-class-kind.ts b/packages/typir/src/kinds/class/top-class-kind.ts index 5368c9b7..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,20 @@ * 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 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 { isKind, Kind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } 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 { @@ -27,28 +32,37 @@ export class TopClassKind implements Kind { 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', // 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 +70,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..068aa1c2 100644 --- a/packages/typir/src/kinds/class/top-class-type.ts +++ b/packages/typir/src/kinds/class/top-class-type.ts @@ -4,25 +4,30 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; +import type { 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 type { 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 { 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; - 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 +37,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 +59,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..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,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 { TypeCheckStrategy } from '../../utils/utils-type-comparison.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 { Kind, isKind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; import { FixedParameterType } from './fixed-parameters-type.js'; export class Parameter { @@ -21,12 +22,13 @@ 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'; @@ -41,38 +43,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', // 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 +101,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 { +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..fcd19484 100644 --- a/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts +++ b/packages/typir/src/kinds/fixed-parameters/fixed-parameters-type.ts @@ -6,10 +6,20 @@ 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 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 { FixedParameterKind, FixedParameterTypeDetails, isFixedParametersKind, Parameter } from './fixed-parameters-kind.js'; +import type { + FixedParameterKind, + FixedParameterTypeDetails, + Parameter, +} from './fixed-parameters-kind.js'; +import { isFixedParametersKind } 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,7 +56,7 @@ export class FixedParameterType extends Type { } getParameterTypes(): Type[] { - return this.parameterValues.map(p => p.type); + return this.parameterValues.map((p) => p.type); } override getName(): 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..15d24e48 100644 --- a/packages/typir/src/kinds/function/function-inference-call.ts +++ b/packages/typir/src/kinds/function/function-inference-call.ts @@ -4,14 +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 { InferenceProblem, InferenceRuleNotApplicable, TypeInferenceResultWithInferringChildren, TypeInferenceRuleWithInferringChildren } from '../../services/inference.js'; -import { TypirServices } from '../../typir.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, +} from '../../services/inference.js'; +import type { 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 { + 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. @@ -25,40 +36,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 +108,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 { @@ -108,14 +159,19 @@ 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 } @@ -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..12cc12e1 100644 --- a/packages/typir/src/kinds/function/function-initializer.ts +++ b/packages/typir/src/kinds/function/function-initializer.ts @@ -4,15 +4,24 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Type, TypeStateListener } from '../../graph/type-node.js'; +import type { 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 type { TypeInferenceRule } from '../../services/inference.js'; +import type { TypirServices } from '../../typir.js'; +import type { InferenceRuleWithOptions } from '../../utils/utils-definitions.js'; +import { + bindInferCurrentTypeRule, + 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 type { + CreateFunctionTypeDetails, + FunctionKind, + FunctionTypeDetails, + InferFunctionCall, +} from './function-kind.js'; +import type { AvailableFunctionsManager } from './function-overloading.js'; import { FunctionType, isFunctionType } from './function-type.js'; /** @@ -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..24ab2872 100644 --- a/packages/typir/src/kinds/function/function-kind.ts +++ b/packages/typir/src/kinds/function/function-kind.ts @@ -4,55 +4,69 @@ * 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 type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { 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 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 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 { FunctionType } from './function-type.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 ... - 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 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 +119,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 +169,18 @@ export interface FunctionConfigurationChain { * - optional parameters * - parameters which are used for output AND input */ -export class FunctionKind implements Kind, FunctionFactoryService { +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,7 +188,9 @@ 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, @@ -170,7 +200,7 @@ export class FunctionKind implements Kind, FunctionFactoryService< typeToInferForCallsOfFunctionsWithoutOutput: 'THROW_ERROR', subtypeParameterChecking: 'SUB_TYPE', // the actually overriden values: - ...options + ...options, }; } @@ -178,31 +208,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}`; } @@ -234,28 +298,40 @@ export class FunctionKind implements Kind, FunctionFactoryService< 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') { // 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,18 +341,30 @@ 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, + ); } } diff --git a/packages/typir/src/kinds/function/function-overloading.ts b/packages/typir/src/kinds/function/function-overloading.ts index f2466544..a4d65ece 100644 --- a/packages/typir/src/kinds/function/function-overloading.ts +++ b/packages/typir/src/kinds/function/function-overloading.ts @@ -4,15 +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 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 { FunctionKind, InferFunctionCall } from './function-kind.js'; -import { FunctionType, isFunctionType } from './function-type.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'; /** @@ -30,18 +31,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 +59,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 +87,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 +117,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 +139,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 +159,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 +172,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 +204,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..c968bcde 100644 --- a/packages/typir/src/kinds/function/function-type.ts +++ b/packages/typir/src/kinds/function/function-type.ts @@ -7,10 +7,20 @@ 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 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'; +import type { FunctionKind, FunctionTypeDetails } from './function-kind.js'; +import { 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,11 +109,15 @@ 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)) { @@ -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) { @@ -151,7 +227,9 @@ export class FunctionType extends Type { } else { switch (notResolvedBehavior) { case 'EXCEPTION': - throw new Error(`Output parameter ${this.outputParameter.name} is not resolved.`); + throw new Error( + `Output parameter ${this.outputParameter.name} is not resolved.`, + ); case 'RETURN_UNDEFINED': return undefined; default: @@ -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..cec4d2e8 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 type { + ValidationProblemAcceptor, + ValidationRuleLifecycle, +} 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'; +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 type { InferFunctionCall } from './function-kind.js'; +import type { + 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, + ); } } } @@ -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,22 +208,34 @@ 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.', + 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! @@ -169,7 +255,12 @@ 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, @@ -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') { 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..cc9d01b4 100644 --- a/packages/typir/src/kinds/function/function-validation-unique.ts +++ b/packages/typir/src/kinds/function/function-validation-unique.ts @@ -4,16 +4,23 @@ * 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 type { + ValidationProblemAcceptor, + ValidationRuleLifecycle, +} 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 { - 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 +28,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,10 +78,14 @@ 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) { diff --git a/packages/typir/src/kinds/kind.ts b/packages/typir/src/kinds/kind.ts index e42bc6e0..cb70e8d5 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..31d66ccf 100644 --- a/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts +++ b/packages/typir/src/kinds/multiplicity/multiplicity-kind.ts @@ -4,16 +4,18 @@ * 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 type { Type, TypeDetails } from '../../graph/type-node.js'; +import type { TypirServices } from '../../typir.js'; import { assertTrue } from '../../utils/utils.js'; -import { Kind, isKind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; import { MultiplicityType } from './multiplicity-type.js'; -export interface MultiplicityTypeDetails extends TypeDetails { - constrainedType: Type, - lowerBound: number, - upperBound: number +export interface MultiplicityTypeDetails + extends TypeDetails { + constrainedType: Type; + lowerBound: number; + upperBound: number; } export interface MultiplicityKindOptions { @@ -32,28 +34,39 @@ export class MultiplicityKind implements Kind { 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: '*', // 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 +74,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 +86,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 +112,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 +124,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 +138,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..b5d51436 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 type { TypirProblem } from '../../utils/utils-definitions.js'; +import { + checkValueForConflict, + createKindConflict, +} from '../../utils/utils-type-comparison.js'; +import type { + MultiplicityKind, + MultiplicityTypeDetails, +} from './multiplicity-kind.js'; +import { isMultiplicityKind } 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..83a4932b 100644 --- a/packages/typir/src/kinds/primitive/primitive-kind.ts +++ b/packages/typir/src/kinds/primitive/primitive-kind.ts @@ -4,81 +4,115 @@ * 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 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 { isKind, Kind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } 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 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 { +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 +121,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..d8fab7a3 100644 --- a/packages/typir/src/kinds/primitive/primitive-type.ts +++ b/packages/typir/src/kinds/primitive/primitive-type.ts @@ -6,14 +6,22 @@ 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 type { TypirProblem } from '../../utils/utils-definitions.js'; +import { + checkValueForConflict, + createKindConflict, +} 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; - 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 +37,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..0257a627 100644 --- a/packages/typir/src/kinds/top/top-kind.ts +++ b/packages/typir/src/kinds/top/top-kind.ts @@ -4,17 +4,21 @@ * 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 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 { isKind, Kind } from '../kind.js'; +import type { Kind } from '../kind.js'; +import { isKind } from '../kind.js'; import { TopType } from './top-type.js'; -export interface TopTypeDetails extends TypeDetails { +export interface TopTypeDetails + extends TypeDetails { // empty } -interface CreateTopTypeDetails extends TopTypeDetails { +interface CreateTopTypeDetails + extends TopTypeDetails { inferenceRules: Array>; } @@ -25,33 +29,44 @@ export interface TopKindOptions { 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 { +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', // the actually overriden values: - ...options + ...options, }; } @@ -60,7 +75,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 +85,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 +113,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..49f4111f 100644 --- a/packages/typir/src/kinds/top/top-type.ts +++ b/packages/typir/src/kinds/top/top-type.ts @@ -4,24 +4,29 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { TypeGraphListener } from '../../graph/type-graph.js'; +import type { 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 type { TypirProblem } from '../../utils/utils-definitions.js'; import { createKindConflict } from '../../utils/utils-type-comparison.js'; -import { isTopKind, TopKind, TopTypeDetails } from './top-kind.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; - 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 +36,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 +58,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..514d7593 100644 --- a/packages/typir/src/services/assignability.ts +++ b/packages/typir/src/services/assignability.ts @@ -4,13 +4,15 @@ * 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'; @@ -21,7 +23,9 @@ export interface AssignabilityProblem extends TypirProblem { subProblems: TypirProblem[]; } export const AssignabilityProblem = 'AssignabilityProblem'; -export function isAssignabilityProblem(problem: unknown): problem is AssignabilityProblem { +export function isAssignabilityProblem( + problem: unknown, +): problem is AssignabilityProblem { return isAssignabilityResult(problem) && problem.result === false; } @@ -32,29 +36,40 @@ export interface AssignabilitySuccess { 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 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 +83,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 +111,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..e48ab3c4 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 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. */ 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,19 +144,20 @@ export class DefaultTypeRelationshipCaching implements TypeRelatio } /** Override this function to store more or less relationships in the type graph. */ - protected storeCachingInformation(value: EdgeCachingInformation | undefined): boolean { + 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; @@ -116,7 +166,9 @@ export interface LanguageNodeInferenceCaching { 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..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 | + | 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 +50,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 +106,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 +128,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) @@ -143,7 +155,9 @@ export class DefaultTypeConversion implements TypeConversion { */ 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.`, + ); } } } @@ -166,10 +180,16 @@ export class DefaultTypeConversion implements TypeConversion { } // check whether there is a transitive relationship (in general, these checks are expensive) - if (this.isTransitive('EXPLICIT') && this.isTransitivelyConvertable(from, to, 'EXPLICIT')) { + if ( + this.isTransitive('EXPLICIT') && + this.isTransitivelyConvertable(from, to, 'EXPLICIT') + ) { return 'EXPLICIT'; } - if (this.isTransitive('IMPLICIT_EXPLICIT') && this.isTransitivelyConvertable(from, to, 'IMPLICIT_EXPLICIT')) { + if ( + this.isTransitive('IMPLICIT_EXPLICIT') && + this.isTransitivelyConvertable(from, to, 'IMPLICIT_EXPLICIT') + ) { return 'IMPLICIT_EXPLICIT'; } @@ -177,19 +197,39 @@ export class DefaultTypeConversion implements TypeConversion { 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); } } @@ -208,14 +248,26 @@ export class DefaultTypeConversion implements TypeConversion { 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); } } diff --git a/packages/typir/src/services/equality.ts b/packages/typir/src/services/equality.ts index 1525ef14..3de47105 100644 --- a/packages/typir/src/services/equality.ts +++ b/packages/typir/src/services/equality.ts @@ -5,11 +5,16 @@ ******************************************************************************/ 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 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'; @@ -18,7 +23,9 @@ export interface TypeEqualityProblem extends TypirProblem { subProblems: TypirProblem[]; // might be empty } export const TypeEqualityProblem = 'TypeEqualityProblem'; -export function isTypeEqualityProblem(problem: unknown): problem is TypeEqualityProblem { +export function isTypeEqualityProblem( + problem: unknown, +): problem is TypeEqualityProblem { return isSpecificTypirProblem(problem, TypeEqualityProblem); } @@ -30,7 +37,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,12 +54,22 @@ 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 linkData = cache.getRelationshipBidirectional( + type1, + type2, + EqualityEdge, + ); const equalityCaching = linkData?.cachingInformation ?? 'UNKNOWN'; - function save(equalityCaching: EdgeCachingInformation, error: TypeEqualityProblem | undefined): void { + function save( + equalityCaching: EdgeCachingInformation, + error: TypeEqualityProblem | undefined, + ): void { const newEdge: EqualityEdge = { $relation: EqualityEdge, from: type1, @@ -57,7 +77,10 @@ export class DefaultTypeEquality implements TypeEquality { cachingInformation: 'LINK_EXISTS', error, }; - cache.setOrUpdateBidirectionalRelationship(newEdge, equalityCaching); + cache.setOrUpdateBidirectionalRelationship( + newEdge, + equalityCaching, + ); } // skip recursive checking @@ -100,11 +123,15 @@ export class DefaultTypeEquality implements TypeEquality { 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,10 +151,9 @@ 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 { diff --git a/packages/typir/src/services/inference.ts b/packages/typir/src/services/inference.ts index 37716b90..5440b373 100644 --- a/packages/typir/src/services/inference.ts +++ b/packages/typir/src/services/inference.ts @@ -5,12 +5,18 @@ ******************************************************************************/ 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 type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { + RuleCollectorListener, + RuleOptions, +} 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 { LanguageNodeInferenceCaching } from './caching.js'; +import type { LanguageNodeInferenceCaching } from './caching.js'; export interface InferenceProblem extends TypirProblem { $problem: 'InferenceProblem'; @@ -21,7 +27,9 @@ export interface InferenceProblem extends TypirProblem { subProblems: TypirProblem[]; // might be missing or empty } export const InferenceProblem = 'InferenceProblem'; -export function isInferenceProblem(problem: unknown): problem is InferenceProblem { +export function isInferenceProblem( + problem: unknown, +): problem is InferenceProblem { return isSpecificTypirProblem(problem, InferenceProblem); } @@ -31,18 +39,18 @@ 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 +61,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 +92,10 @@ export interface TypeInferenceRuleWithInferringChildren): TypeInferenceResultWithInferringChildren; + inferTypeWithoutChildren( + languageNode: InputType, + typir: TypirServices, + ): TypeInferenceResultWithInferringChildren; /** * 2nd step is to finally decide about the inferred type. @@ -84,13 +108,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 +142,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 +153,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 +164,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 +198,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 +210,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 +220,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 +251,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); @@ -205,29 +276,41 @@ export class DefaultTypeInferenceCollector implements TypeInferenc } } - 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 } @@ -249,21 +332,35 @@ export class DefaultTypeInferenceCollector implements TypeInferenc return collectedInferenceProblems; } - protected executeSingleInferenceRuleLogic(rule: TypeInferenceRule, languageNode: LanguageType, collectedInferenceProblems: Array>): Type | undefined { + 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); + 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)) { @@ -287,7 +384,12 @@ export class DefaultTypeInferenceCollector implements TypeInferenc 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 +400,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 +436,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 +494,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 +502,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 +528,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)) { @@ -427,11 +553,18 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferen } } - inferTypeWithChildrensTypes(_languageNode: LanguageType, _childrenTypes: Array, _typir: TypirServices): Type | InferenceProblem { + 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 +575,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 +594,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..eb6e1151 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 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; - getOrCreateKind(type: T['$name'], factory: (services: TypirServices) => T): T; + 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) @@ -35,10 +40,13 @@ export class DefaultKindRegistry implements KindRegistry(type: T['$name']): T | undefined { - return this.kinds.get(type) as (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..ee08166c 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 { +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 +71,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 +86,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 +102,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 +112,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 +179,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 +242,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 +255,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 +292,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 +305,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 +343,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 +356,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 +395,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 +416,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[] { + 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)]; + ? ( + inferenceRule as InferOperatorWithMultipleOperands + ).operands(languageNode, this.typeDetails.name) + : [ + ( + inferenceRule as InferOperatorWithSingleOperand + ).operand(languageNode, this.typeDetails.name), + ]; } protected getFunctionFactory(): FunctionFactoryService { @@ -317,11 +489,7 @@ 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); diff --git a/packages/typir/src/services/printing.ts b/packages/typir/src/services/printing.ts index 6ad9390c..bdcfde6f 100644 --- a/packages/typir/src/services/printing.ts +++ b/packages/typir/src/services/printing.ts @@ -4,15 +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 { IndexedTypeConflict, ValueConflict, isIndexedTypeConflict, isValueConflict } from '../utils/utils-type-comparison.js'; +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'; +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; @@ -21,12 +33,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 +60,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,7 +82,10 @@ 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 = ''; @@ -96,7 +114,10 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< 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,14 +131,20 @@ 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`; @@ -129,8 +156,12 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter< 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,10 +188,13 @@ 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 { + printLanguageNode( + languageNode: LanguageType, + sentenceBegin: boolean = false, + ): string { return `${sentenceBegin ? 'T' : 't'}he language node '${languageNode}'`; } @@ -172,7 +206,11 @@ 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); diff --git a/packages/typir/src/services/subtype.ts b/packages/typir/src/services/subtype.ts index e3592f26..1c2c9641 100644 --- a/packages/typir/src/services/subtype.ts +++ b/packages/typir/src/services/subtype.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 { 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'; @@ -38,10 +39,13 @@ export function isSubTypeSuccess(success: unknown): success is SubTypeSuccess { export type SubTypeResult = SubTypeSuccess | SubTypeProblem; 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 +62,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 +95,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 +129,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) { @@ -144,13 +167,18 @@ export class DefaultSubType implements SubType { // 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 { diff --git a/packages/typir/src/services/validation.ts b/packages/typir/src/services/validation.ts index d83d75c9..e556aa13 100644 --- a/packages/typir/src/services/validation.ts +++ b/packages/typir/src/services/validation.ts @@ -4,18 +4,28 @@ * 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 type { Type } from '../graph/type-node.js'; +import { isType } from '../graph/type-node.js'; +import type { TypirServices } from '../typir.js'; +import type { + RuleCollectorListener, + RuleOptions, +} 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 { TypeInferenceCollector } from './inference.js'; -import { ProblemPrinter } from './printing.js'; +import type { TypeInferenceCollector } from './inference.js'; +import type { ProblemPrinter } from './printing.js'; export type Severity = 'error' | 'warning' | 'info' | 'hint'; -export interface ValidationMessageDetails { +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 +33,38 @@ export interface ValidationMessageDetails extends ValidationMessageDetails, TypirProblem { +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 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 +72,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 +87,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 +178,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] + 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! + 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 +315,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 +336,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 +398,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 +411,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 +465,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 +484,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 { + addValidationRule( + rule: ValidationRule, + givenOptions?: Partial, + ): void { if (typeof rule === 'function') { - this.ruleRegistryFunctional.addRule(rule as ValidationRuleFunctional, givenOptions); + 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 { + removeValidationRule( + rule: ValidationRule, + givenOptions?: Partial, + ): void { if (typeof rule === 'function') { - this.ruleRegistryFunctional.removeRule(rule as ValidationRuleFunctional, givenOptions); + this.ruleRegistryFunctional.removeRule( + rule as ValidationRuleFunctional, + givenOptions, + ); } else { - this.ruleRegistryLifecycle.removeRule(rule as ValidationRuleLifecycle, givenOptions); + this.ruleRegistryLifecycle.removeRule( + rule as ValidationRuleLifecycle, + givenOptions, + ); } } @@ -320,39 +542,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 +615,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 +637,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..6d08baf9 100644 --- a/packages/typir/src/test/predefined-language-nodes.ts +++ b/packages/typir/src/test/predefined-language-nodes.ts @@ -5,17 +5,14 @@ ******************************************************************************/ import { DefaultLanguageService } from '../services/language.js'; -import { InferOperatorWithMultipleOperands } from '../services/operator.js'; +import type { InferOperatorWithMultipleOperands } from '../services/operator.js'; import { DefaultTypeConflictPrinter } from '../services/printing.js'; -/* eslint-disable @typescript-eslint/parameter-properties */ - /** * Base class for all language nodes, * which are predefined for test cases. */ export abstract class TestLanguageNode { - constructor() { // empty } @@ -31,7 +28,9 @@ export abstract class TestLanguageNode { 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 +38,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 @@ -91,65 +86,74 @@ 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()}'`; } @@ -158,7 +162,9 @@ export class TestProblemPrinter extends DefaultTypeConflictPrinter { - 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..eb6d8c82 100644 --- a/packages/typir/src/typir.ts +++ b/packages/typir/src/typir.ts @@ -4,26 +4,63 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { DefaultGraphAlgorithms, GraphAlgorithms } from './graph/graph-algorithms.js'; +import type { GraphAlgorithms } from './graph/graph-algorithms.js'; +import { DefaultGraphAlgorithms } 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 type { TypeResolvingService } from './initialization/type-selector.js'; +import { DefaultTypeResolver } from './initialization/type-selector.js'; +import type { BottomFactoryService } from './kinds/bottom/bottom-kind.js'; +import { BottomKind, BottomKindName } from './kinds/bottom/bottom-kind.js'; +import type { ClassFactoryService } from './kinds/class/class-kind.js'; +import { ClassKind, ClassKindName } from './kinds/class/class-kind.js'; +import type { FunctionFactoryService } from './kinds/function/function-kind.js'; +import { + FunctionKind, + FunctionKindName, +} from './kinds/function/function-kind.js'; +import type { PrimitiveFactoryService } from './kinds/primitive/primitive-kind.js'; +import { + PrimitiveKind, + PrimitiveKindName, +} from './kinds/primitive/primitive-kind.js'; +import type { TopFactoryService } from './kinds/top/top-kind.js'; +import { TopKind, TopKindName } from './kinds/top/top-kind.js'; +import type { TypeAssignability } from './services/assignability.js'; +import { DefaultTypeAssignability } from './services/assignability.js'; +import type { + LanguageNodeInferenceCaching, + TypeRelationshipCaching, +} from './services/caching.js'; +import { + DefaultLanguageNodeInferenceCaching, + DefaultTypeRelationshipCaching, +} from './services/caching.js'; +import type { TypeConversion } from './services/conversion.js'; +import { DefaultTypeConversion } from './services/conversion.js'; +import type { TypeEquality } from './services/equality.js'; +import { DefaultTypeEquality } from './services/equality.js'; +import type { TypeInferenceCollector } from './services/inference.js'; +import { DefaultTypeInferenceCollector } from './services/inference.js'; +import type { KindRegistry } from './services/kind-registry.js'; +import { DefaultKindRegistry } from './services/kind-registry.js'; +import type { LanguageService } from './services/language.js'; +import { DefaultLanguageService } from './services/language.js'; +import type { OperatorFactoryService } from './services/operator.js'; +import { DefaultOperatorFactory } from './services/operator.js'; +import type { ProblemPrinter } from './services/printing.js'; +import { DefaultTypeConflictPrinter } from './services/printing.js'; +import type { SubType } from './services/subtype.js'; +import { DefaultSubType } from './services/subtype.js'; +import type { + ValidationCollector, + ValidationConstraints, +} from './services/validation.js'; +import { + DefaultValidationCollector, + DefaultValidationConstraints, +} from './services/validation.js'; +import type { Module } from './utils/dependency-injection.js'; +import { inject } from './utils/dependency-injection.js'; /** * Some design decisions for Typir: @@ -73,7 +110,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 +120,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 +178,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, + ); } /** @@ -127,12 +204,16 @@ 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 -export type DeepPartial = T[keyof T] extends Function ? T : { - [P in keyof T]?: DeepPartial; -} +//eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +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..e398038a 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,10 +48,39 @@ 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); } @@ -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..b78093a8 100644 --- a/packages/typir/src/utils/rule-registration.ts +++ b/packages/typir/src/utils/rule-registration.ts @@ -2,11 +2,11 @@ * 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 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 { @@ -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; @@ -325,23 +379,33 @@ 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'); diff --git a/packages/typir/src/utils/test-utils.ts b/packages/typir/src/utils/test-utils.ts index 37cfbe8c..93fe7446 100644 --- a/packages/typir/src/utils/test-utils.ts +++ b/packages/typir/src/utils/test-utils.ts @@ -5,11 +5,19 @@ ******************************************************************************/ 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 type { Type } from '../graph/type-node.js'; +import type { TestLanguageNode } from '../test/predefined-language-nodes.js'; +import { + TestLanguageService, + TestProblemPrinter, +} from '../test/predefined-language-nodes.js'; +import type { PartialTypirServices, TypirServices } from '../typir.js'; +import { + createDefaultTypirServicesModule, + createTypirServices, +} 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. @@ -20,33 +28,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 +81,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 +94,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 +121,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 +134,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 +161,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 +174,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 +200,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 +220,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); @@ -183,9 +291,13 @@ function compareValidationIssuesLogic(actualIssues: string[], expectedErrors: st 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 +312,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; } @@ -226,7 +345,9 @@ function compareValidationIssuesLogicAbsent(actualIssues: string[], forbiddenErr 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 +358,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 +365,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..d333d99f 100644 --- a/packages/typir/src/utils/utils-definitions.ts +++ b/packages/typir/src/utils/utils-definitions.ts @@ -4,13 +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 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 { InferenceRuleNotApplicable } from '../services/inference.js'; +import type { + ValidationProblemAcceptor, + ValidationRule, + ValidationRuleOptions, +} from '../services/validation.js'; +import type { TypirServices } from '../typir.js'; import { toArray } from './utils.js'; /** @@ -20,40 +27,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 +89,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 +109,10 @@ export function bindValidateCurrentTypeRule; } - // // 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 +150,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 +180,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 +250,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..81987e6f 100644 --- a/packages/typir/src/utils/utils-type-comparison.ts +++ b/packages/typir/src/utils/utils-type-comparison.ts @@ -5,19 +5,24 @@ ******************************************************************************/ 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 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 { isNameTypePair, isSpecificTypirProblem, NameTypePair, TypirProblem } from './utils-definitions.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 { +export function createTypeCheckStrategy( + strategy: TypeCheckStrategy, + typir: TypirServices, +): (t1: Type, t2: Type) => TypirProblem | undefined { switch (strategy) { case 'ASSIGNABLE_TYPE': return typir.Assignability.getAssignabilityProblem // t1 === source, t2 === target @@ -28,8 +33,8 @@ export function createTypeCheckStrategy(strategy: TypeCheckStrateg 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); } @@ -47,21 +52,28 @@ 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; } @@ -87,14 +99,24 @@ export interface IndexedTypeConflict extends TypirProblem { subProblems: TypirProblem[]; } export const IndexedTypeConflict = 'IndexedTypeConflict'; -export function isIndexedTypeConflict(problem: unknown): problem is 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 +161,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 +175,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 +191,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 +248,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 +318,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 +327,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,7 +342,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: sourceType, actual: targetType, propertyName: name, - subProblems: [relationCheckResult] + subProblems: [relationCheckResult], }); } else { // same type @@ -307,7 +360,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: sourceType, actual: undefined, propertyName: name, - subProblems: [] + subProblems: [], }); } } @@ -322,7 +375,7 @@ export function checkNameTypesMap(sourceFields: Map, tar expected: undefined, actual, propertyName: index, - subProblems: [] + subProblems: [], }); } } @@ -337,7 +390,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..6cc2bfe5 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 { Type } from '../graph/type-node.js'; +import type { 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; } @@ -54,7 +66,11 @@ export function assertUnreachable(_: never): never { 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..bd61e4a6 100644 --- a/packages/typir/test/api-example.test.ts +++ b/packages/typir/test/api-example.test.ts @@ -4,40 +4,65 @@ * 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 { InferOperatorWithMultipleOperands } from '../src/services/operator.js'; +import type { InferOperatorWithMultipleOperands } from '../src/services/operator.js'; import { createTypirServices } from '../src/typir.js'; 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 +72,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 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 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 +143,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 +158,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..9d9cb894 100644 --- a/packages/typir/test/kinds/class/class.test.ts +++ b/packages/typir/test/kinds/class/class.test.ts @@ -7,16 +7,30 @@ 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 { + 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', () => { 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'); @@ -24,57 +38,105 @@ describe('Tests some details for class types', () => { 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}'.` }); + accept({ + languageNode: node, + severity: 'error', + message: `Validated access of 'fieldBoolean' of the variable '${node.classVariable.name}'.`, + }); } }, }) - .finish().getTypeFinal(); + .finish() + .getTypeFinal(); assertTypirType(classType1, isClassType, 'MyClass1'); - typir.Inference.addInferenceRule((node: Variable) => node.initialValue, { languageKey: Variable.name }); // infer the type of variables - + 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..546639aa 100644 --- a/packages/typir/test/kinds/function/operator-inference-call.test.ts +++ b/packages/typir/test/kinds/function/operator-inference-call.test.ts @@ -6,13 +6,30 @@ 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'; +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, + integer123, + integer456, + IntegerLiteral, + string123, + string456, + StringLiteral, +} from '../../../src/test/predefined-language-nodes.js'; +import type { 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; @@ -22,22 +39,50 @@ 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'); }); @@ -52,17 +97,42 @@ describe('Tests some special cases for (overloaded) operator calls', () => { 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(); @@ -74,40 +144,91 @@ describe('Tests some special cases for (overloaded) operator calls', () => { 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..b0ef9cf1 100644 --- a/packages/typir/test/kinds/function/operator-overloaded.test.ts +++ b/packages/typir/test/kinds/function/operator-overloaded.test.ts @@ -4,18 +4,44 @@ * 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 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 type { 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 type { SubTypeEdge } from '../../../src/services/subtype.js'; +import type { + TestExpressionNode, + TestLanguageNode, +} from '../../../src/test/predefined-language-nodes.js'; +import { + AssignmentStatement, + BinaryExpression, + booleanFalse, + BooleanLiteral, + booleanTrue, + double2_0, + double3_0, + DoubleLiteral, + InferenceRuleBinaryExpression, + integer2, + integer3, + IntegerLiteral, + string2, + string3, + StringLiteral, + Variable, +} 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'; describe('Multiple best matches for overloaded operators', () => { @@ -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,13 +114,18 @@ 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', () => { expectAssignmentValid(integerType, integerType); @@ -89,11 +150,21 @@ describe('Multiple best matches for overloaded operators', () => { expectAssignmentError(stringType, doubleType); }); test('boolean to double', () => { - expectAssignmentValid(booleanType, doubleType, 'ConversionEdge', 'SubTypeEdge'); + expectAssignmentValid( + booleanType, + doubleType, + 'ConversionEdge', + 'SubTypeEdge', + ); }); test('integer to string', () => { - expectAssignmentValid(integerType, stringType, 'SubTypeEdge', 'ConversionEdge'); + expectAssignmentValid( + integerType, + stringType, + 'SubTypeEdge', + 'ConversionEdge', + ); }); test('double to string', () => { expectAssignmentValid(doubleType, stringType, 'ConversionEdge'); @@ -102,7 +173,13 @@ describe('Multiple best matches for overloaded operators', () => { expectAssignmentValid(stringType, stringType); }); test('boolean to string', () => { - expectAssignmentValid(booleanType, stringType, 'ConversionEdge', 'SubTypeEdge', 'ConversionEdge'); + expectAssignmentValid( + booleanType, + stringType, + 'ConversionEdge', + 'SubTypeEdge', + 'ConversionEdge', + ); }); test('integer to boolean', () => { @@ -118,13 +195,22 @@ describe('Multiple best matches for overloaded operators', () => { 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,13 +226,19 @@ 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'); @@ -180,15 +272,26 @@ describe('Multiple best matches for overloaded operators', () => { expectOverload(integer2, string3, 'string'); }); - - function expectOverload(left: TestExpressionNode, right: TestExpressionNode, typeName: 'string'|'integer'|'double'|'boolean'): void { + 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 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..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 @@ -5,9 +5,22 @@ ******************************************************************************/ 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 type { PrimitiveType } from '../../../src/kinds/primitive/primitive-type.js'; +import type { + TestExpressionNode, + TestLanguageNode, +} from '../../../src/test/predefined-language-nodes.js'; +import { + BinaryExpression, + booleanFalse, + BooleanLiteral, + booleanTrue, + InferenceRuleBinaryExpression, + integer123, + integer456, + IntegerLiteral, +} 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', () => { @@ -19,31 +32,87 @@ 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('true + false: wrong, since this signature does not exist', () => { - expectOperatorCallError(booleanTrue, '+', booleanFalse, "The type 'boolean' is not assignable to the type 'integer'."); + expectOperatorCallError( + booleanTrue, + '+', + booleanFalse, + "The type 'boolean' is not assignable to the type 'integer'.", + ); }); // &&: only booleans are supported @@ -62,19 +131,37 @@ describe('Tests the "validateArgumentsOfCalls" option to check the given argumen expectOperatorCallValid(booleanTrue, '==', booleanFalse); }); test('123 == false: wrong, since this signature is not defined', () => { - expectOperatorCallError(integer123, '==', booleanFalse, 'is not assignable to the type'); + 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'); + 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 +169,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..0bd1f945 100644 --- a/packages/typir/test/kinds/primitive/primitive.test.ts +++ b/packages/typir/test/kinds/primitive/primitive.test.ts @@ -5,20 +5,32 @@ ******************************************************************************/ import { beforeEach, describe, expect, test } from 'vitest'; -import { createTypirServicesForTesting, expectTypirTypes } from '../../../src/utils/test-utils.js'; +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 type { TestLanguageNode } from '../../../src/test/predefined-language-nodes.js'; +import { + integer123, + IntegerLiteral, + stringHello, + StringLiteral, +} 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', () => { const typir = createTypirServicesForTesting(); - const integerType1 = typir.factory.Primitives.create({ primitiveName: 'integer' }).finish(); + 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' }); + const integerType2 = typir.factory.Primitives.get({ + primitiveName: 'integer', + }); assertTypirType(integerType2, isPrimitiveType, 'integer'); expect(integerType1).toBe(integerType2); }); @@ -26,11 +38,16 @@ describe('Tests some details for primitive types', () => { 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(); + 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', () => { @@ -39,28 +56,43 @@ describe('Tests some details for primitive types', () => { 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 + 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'); }); test('String value without validation issue', () => { - assertTypirType(typir.Inference.inferType(stringHello), isPrimitiveType, 'integer'); // test the successful inference + 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..390c9895 100644 --- a/packages/typir/test/services/conversion.test.ts +++ b/packages/typir/test/services/conversion.test.ts @@ -8,16 +8,29 @@ 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', () => { 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..4f352e31 100644 --- a/packages/typir/test/services/inference-registry.test.ts +++ b/packages/typir/test/services/inference-registry.test.ts @@ -2,16 +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 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, +} 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, +} 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)', () => { @@ -31,28 +48,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) { @@ -68,9 +89,7 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( }; }); - describe('Simple inference rules', () => { - test('add String rule without any options', () => { assertNumberRules(0); addInferenceRule(ruleString); @@ -87,42 +106,52 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( }); test('add rule for Strings only', () => { - addInferenceRule(ruleStringInteger, { languageKey: StringLiteral.name }); + 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 }); + 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] }); + 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', () => { assertNumberRules(0); composite.addInferenceRule(ruleString); @@ -155,15 +184,21 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( }); test('remove rule with multiple keys from composite', () => { - composite.addInferenceRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name] }); + 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); @@ -175,7 +210,9 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( 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); @@ -191,7 +228,9 @@ describe('Tests the logic for registering rules (applied to inference rules)', ( 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 +243,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 +270,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 +304,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..12d4eaef 100644 --- a/packages/typir/test/services/validation-registry.test.ts +++ b/packages/typir/test/services/validation-registry.test.ts @@ -2,16 +2,32 @@ * 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'; +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'; +import { DefaultValidationCollector } from '../../src/services/validation.js'; +import type { TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; +import { + booleanTrue, + integer123, + IntegerLiteral, + stringHello, + StringLiteral, +} 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'; describe('Tests the logic for registering rules (applied to state-less validation rules)', () => { let typir: TypirServices; @@ -26,31 +42,59 @@ 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}`, + }); } }; }); @@ -81,65 +125,85 @@ describe('Tests the logic for registering rules (applied to state-less validatio 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, booleanTrue, [ + 'failure3-BooleanLiteral', + ]); // generic message for everything else than strings and integers }); test('String+Integer rule registered for String', () => { - addValidationRule(ruleStringInteger, { languageKey: StringLiteral.name }); + 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 }); + addValidationRule(ruleStringInteger, { + languageKey: IntegerLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, ['i3-123']); expectValidationIssuesStrict(typir, booleanTrue, []); }); test('String+Integer rule registered for String and Integer', () => { - addValidationRule(ruleStringInteger, { languageKey: [StringLiteral.name, IntegerLiteral.name] }); + 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, { }); + 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', () => { addValidationRule(ruleString, { languageKey: StringLiteral.name }); - addValidationRule(ruleInteger, { languageKey: IntegerLiteral.name }); + 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, { }); + 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', () => { 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 }); + addValidationRule(ruleStringInteger, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); addValidationRule(ruleStringInteger, { languageKey: undefined }); @@ -147,25 +211,27 @@ describe('Tests the logic for registering rules (applied to state-less validatio expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, ['i3-123']); }); - }); - 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 }); + 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']); @@ -174,12 +240,13 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); 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 }); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); }); test('Removing a rule (which was added twice)', () => { @@ -187,30 +254,42 @@ describe('Tests the logic for registering rules (applied to state-less validatio addValidationRule(ruleString, { languageKey: StringLiteral.name }); addValidationRule(ruleString, { languageKey: StringLiteral.name }); expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + 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 }); + removeValidationRule(ruleString, { + languageKey: StringLiteral.name, + }); expectValidationIssuesStrict(typir, stringHello, []); addValidationRule(ruleString, { languageKey: StringLiteral.name }); expectValidationIssuesStrict(typir, stringHello, ['s1-Hello']); - removeValidationRule(ruleString, { languageKey: StringLiteral.name }); + 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']); }); 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, integer123, []); @@ -221,9 +300,7 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); }); - describe('bound to type', () => { - test('remove bound rule automatically ("undefined" as language key)', () => { addValidationRule(ruleStringInteger, { boundToType: stringType }); assertNumberRules(1); @@ -236,7 +313,10 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); test('remove bound rule automatically (one dedicated language key: String)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name] }); + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [StringLiteral.name], + }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, []); @@ -247,7 +327,10 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); test('remove bound rule automatically (one dedicated language key: Integer)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [IntegerLiteral.name] }); + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [IntegerLiteral.name], + }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, []); expectValidationIssuesStrict(typir, integer123, ['i3-123']); @@ -258,7 +341,10 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); test('remove bound rule automatically (multiple dedicated language keys)', () => { - addValidationRule(ruleStringInteger, { boundToType: stringType, languageKey: [StringLiteral.name, IntegerLiteral.name] }); + addValidationRule(ruleStringInteger, { + boundToType: stringType, + languageKey: [StringLiteral.name, IntegerLiteral.name], + }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, ['i3-123']); @@ -270,7 +356,9 @@ describe('Tests the logic for registering rules (applied to state-less validatio }); test('remove bound rule automatically, when the last type is removed', () => { - addValidationRule(ruleStringInteger, { boundToType: [stringType, integerType] }); + addValidationRule(ruleStringInteger, { + boundToType: [stringType, integerType], + }); assertNumberRules(1); expectValidationIssuesStrict(typir, stringHello, ['s3-Hello']); expectValidationIssuesStrict(typir, integer123, ['i3-123']); @@ -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..1951fe89 100644 --- a/packages/typir/test/type-definitions.test.ts +++ b/packages/typir/test/type-definitions.test.ts @@ -2,12 +2,15 @@ * 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 { + 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'; @@ -19,29 +22,60 @@ describe('Tests for Typir', () => { 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', fields: [ { name: 'firstName', type: typeString }, { name: 'lastName', type: typeOneOrTwoStrings }, - { name: 'age', type: typeInt } + { name: 'age', type: typeInt }, ], methods: [], }).finish(); @@ -49,59 +83,114 @@ describe('Tests for Typir', () => { const typeStudent = typir.factory.Classes.create({ 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', 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'); // 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..07602f84 100644 --- a/packages/typir/test/utils/test-utils.test.ts +++ b/packages/typir/test/utils/test-utils.test.ts @@ -5,9 +5,21 @@ ******************************************************************************/ 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 type { TestLanguageNode } from '../../src/test/predefined-language-nodes.js'; +import { + integer123, + IntegerLiteral, + StatementBlock, + TestExpressionNode, +} 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'; describe('Test cases for the "expectValidationIssues*(...)" test utilities', () => { let typir: TypirServices; @@ -16,10 +28,18 @@ 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', + }); } }); }); @@ -28,29 +48,55 @@ describe('Test cases for the "expectValidationIssues*(...)" test utilities', () 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']); + expectValidationIssues(typir, integer123, [ + 'found Integer literal', + 'found Expression', + ]); }); 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']); + 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']); + 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' }, []); + expectValidationIssuesStrict( + typir, + integer123, + { severity: 'warning' }, + [], + ); }); test.fails('strict (fails: too less)', () => { - expectValidationIssuesStrict(typir, integer123, ['found Integer literal']); + expectValidationIssuesStrict(typir, integer123, [ + 'found Integer literal', + ]); }); test.fails('strict (fails: too much)', () => { - expectValidationIssuesStrict(typir, integer123, ['found Integer literal', 'found Expression', 'found WhatEverNode']); + expectValidationIssuesStrict(typir, integer123, [ + 'found Integer literal', + 'found Expression', + 'found WhatEverNode', + ]); }); test('absent (only a absent issue)', () => { @@ -60,7 +106,12 @@ describe('Test cases for the "expectValidationIssues*(...)" test utilities', () expectValidationIssuesAbsent(typir, integer123, ['found Expression']); }); test('absent (the specified issue occurs as error, not as warning)', () => { - expectValidationIssuesAbsent(typir, integer123, { severity: 'warning' }, ['found Expression']); + expectValidationIssuesAbsent( + typir, + integer123, + { severity: 'warning' }, + ['found Expression'], + ); }); test('absent (works even for an empty array)', () => { expectValidationIssuesAbsent(typir, integer123, []); @@ -75,5 +126,4 @@ describe('Test cases for the "expectValidationIssues*(...)" test utilities', () 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..c94c864f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ exclude: ['**/generated'], }, deps: { - interopDefault: true - } - } + interopDefault: true, + }, + }, });