diff --git a/.gitignore b/.gitignore index 4afed9191..5081f591a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,10 @@ package-lock.json yarn.lock /.vs typings/types.d.ts -typings/promiseBasedTypes.d.ts \ No newline at end of file +typings/promiseBasedTypes.d.ts +# Ignore generated .d.ts files (but keep hand-written ones) +typings/docs/ +typings/lib/ +typings/translations/*.d.ts +!typings/translations/index.d.ts +!typings/translations/utils.d.ts \ No newline at end of file diff --git a/package.json b/package.json index aca6067ef..f77d8fd5e 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,6 @@ "ts-morph": "27.0.2", "ts-node": "10.9.2", "tsd": "^0.33.0", - "tsd-jsdoc": "2.5.0", "tsx": "^4.19.2", "typedoc": "0.28.13", "typedoc-plugin-markdown": "4.9.0", diff --git a/runok.cjs b/runok.cjs index 44181ce6a..d1b735e2f 100755 --- a/runok.cjs +++ b/runok.cjs @@ -35,12 +35,11 @@ module.exports = { }, async defTypings() { - console.log('Generate TypeScript definition') - // Generate definitions for promised-based helper methods - await npx('jsdoc -c typings/jsdocPromiseBased.conf.json') - fs.renameSync('typings/types.d.ts', 'typings/promiseBasedTypes.d.ts') - // Generate all other regular definitions - await npx('jsdoc -c typings/jsdoc.conf.json') + console.log('Generate TypeScript definitions using TypeScript compiler') + // Generate type definitions using TypeScript compiler (replaces tsd-jsdoc) + // First generate promise-based helper types, then regular types + await npx('node typings/generate-dts.mjs tsconfig.typings.json --promise-based') + await npx('node typings/generate-dts.mjs tsconfig.typings.json') }, async docsPlugins() { diff --git a/typings/Mocha.d.ts b/typings/Mocha.d.ts index cb73a2a1c..a72c7a203 100644 --- a/typings/Mocha.d.ts +++ b/typings/Mocha.d.ts @@ -1,28 +1,28 @@ declare namespace Mocha { class SuiteRunnable { - private _beforeEach; - private _beforeAll; - private _afterEach; - private _afterAll; - private _timeout; - private _enableTimeouts; - private _slow; - private _bail; - private _retries; - private _onlyTests; - private _onlySuites; - - constructor(title: string, parentContext?: Context); - - ctx: Context; - suites: Suite[]; - tests: Test[]; - pending: boolean; - file?: string; - root: boolean; - delayed: boolean; - parent: Suite | undefined; - title: string; + private _beforeEach + private _beforeAll + private _afterEach + private _afterAll + private _timeout + private _enableTimeouts + private _slow + private _bail + private _retries + private _onlyTests + private _onlySuites + + constructor(title: string, parentContext?: Context) + + ctx: Context + suites: Suite[] + tests: Test[] + pending: boolean + file?: string + root: boolean + delayed: boolean + parent: Suite | undefined + title: string /** * Create a new `Suite` with the given `title` and parent `Suite`. When a suite @@ -31,161 +31,161 @@ declare namespace Mocha { * * @see https://mochajs.org/api/mocha#.exports.create */ - static create(parent: Suite, title: string): Suite; + static create(parent: Suite, title: string): Suite /** * Return a clone of this `Suite`. * * @see https://mochajs.org/api/Mocha.Suite.html#clone */ - clone(): Suite; + clone(): Suite /** * Get timeout `ms`. * * @see https://mochajs.org/api/Mocha.Suite.html#timeout */ - timeout(): number; + timeout(): number /** * Set timeout `ms` or short-hand such as "2s". * * @see https://mochajs.org/api/Mocha.Suite.html#timeout */ - timeout(ms: string | number): this; + timeout(ms: string | number): this /** * Get number of times to retry a failed test. * * @see https://mochajs.org/api/Mocha.Suite.html#retries */ - retries(): number; + retries(): number /** * Set number of times to retry a failed test. * * @see https://mochajs.org/api/Mocha.Suite.html#retries */ - retries(n: string | number): this; + retries(n: string | number): this /** * Get whether timeouts are enabled. * * @see https://mochajs.org/api/Mocha.Suite.html#enableTimeouts */ - enableTimeouts(): boolean; + enableTimeouts(): boolean /** * Set whether timeouts are `enabled`. * * @see https://mochajs.org/api/Mocha.Suite.html#enableTimeouts */ - enableTimeouts(enabled: boolean): this; + enableTimeouts(enabled: boolean): this /** * Get slow `ms`. * * @see https://mochajs.org/api/Mocha.Suite.html#slow */ - slow(): number; + slow(): number /** * Set slow `ms` or short-hand such as "2s". * * @see https://mochajs.org/api/Mocha.Suite.html#slow */ - slow(ms: string | number): this; + slow(ms: string | number): this /** * Get whether to bail after first error. * * @see https://mochajs.org/api/Mocha.Suite.html#bail */ - bail(): boolean; + bail(): boolean /** * Set whether to bail after first error. * * @see https://mochajs.org/api/Mocha.Suite.html#bail */ - bail(bail: boolean): this; + bail(bail: boolean): this /** * Check if this suite or its parent suite is marked as pending. * * @see https://mochajs.org/api/Mocha.Suite.html#isPending */ - isPending(): boolean; + isPending(): boolean /** * Run `fn(test[, done])` before running tests. * * @see https://mochajs.org/api/Mocha.Suite.html#beforeAll */ - beforeAll(fn?: Func | AsyncFunc): this; + beforeAll(fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` before running tests. * * @see https://mochajs.org/api/Mocha.Suite.html#beforeAll */ - beforeAll(title: string, fn?: Func | AsyncFunc): this; + beforeAll(title: string, fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` after running tests. * * @see https://mochajs.org/api/Mocha.Suite.html#afterAll */ - afterAll(fn?: Func | AsyncFunc): this; + afterAll(fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` after running tests. * * @see https://mochajs.org/api/Mocha.Suite.html#afterAll */ - afterAll(title: string, fn?: Func | AsyncFunc): this; + afterAll(title: string, fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` before each test case. * * @see https://mochajs.org/api/Mocha.Suite.html#beforeEach */ - beforeEach(fn?: Func | AsyncFunc): this; + beforeEach(fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` before each test case. * * @see https://mochajs.org/api/Mocha.Suite.html#beforeEach */ - beforeEach(title: string, fn?: Func | AsyncFunc): this; + beforeEach(title: string, fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` after each test case. * * @see https://mochajs.org/api/Mocha.Suite.html#afterEach */ - afterEach(fn?: Func | AsyncFunc): this; + afterEach(fn?: Func | AsyncFunc): this /** * Run `fn(test[, done])` after each test case. * * @see https://mochajs.org/api/Mocha.Suite.html#afterEach */ - afterEach(title: string, fn?: Func | AsyncFunc): this; + afterEach(title: string, fn?: Func | AsyncFunc): this /** * Add a test `suite`. * * @see https://mochajs.org/api/Mocha.Suite.html#addSuite */ - addSuite(suite: Suite): this; + addSuite(suite: Suite): this /** * Add a `test` to this suite. * * @see https://mochajs.org/api/Mocha.Suite.html#addTest */ - addTest(test: Test): this; + addTest(test: Test): this /** * Return the full title generated by recursively concatenating the parent's @@ -193,7 +193,7 @@ declare namespace Mocha { * * @see https://mochajs.org/api/Mocha.Suite.html#.Suite#fullTitle */ - fullTitle(): string; + fullTitle(): string /** * Return the title path generated by recursively concatenating the parent's @@ -201,14 +201,14 @@ declare namespace Mocha { * * @see https://mochajs.org/api/Mocha.Suite.html#.Suite#titlePath */ - titlePath(): string[]; + titlePath(): string[] /** * Return the total number of tests. * * @see https://mochajs.org/api/Mocha.Suite.html#.Suite#total */ - total(): number; + total(): number /** * Iterates through each suite recursively to find all tests. Applies a @@ -216,301 +216,301 @@ declare namespace Mocha { * * @see https://mochajs.org/api/Mocha.Suite.html#eachTest */ - eachTest(fn: (test: Test) => void): this; + eachTest(fn: (test: Test) => void): this /** * This will run the root suite if we happen to be running in delayed mode. * * @see https://mochajs.org/api/Mocha.Suite.html#run */ - run(): void; + run(): void /** * Generic hook-creator. */ - protected _createHook(title: string, fn?: Func | AsyncFunc): Hook; + protected _createHook(title: string, fn?: Func | AsyncFunc): Hook } + /** + * Initialize a new `Hook` with the given `title` and callback `fn` + * + * @see https://mochajs.org/api/Hook.html + */ + class Hook extends Runnable { + private _error + + type: 'hook' + originalTitle?: string // added by Runner + /** - * Initialize a new `Hook` with the given `title` and callback `fn` + * Get the test `err`. * - * @see https://mochajs.org/api/Hook.html + * @see https://mochajs.org/api/Hook.html#error */ - class Hook extends Runnable { - private _error; + error(): any - type: "hook"; - originalTitle?: string; // added by Runner + /** + * Set the test `err`. + * + * @see https://mochajs.org/api/Hook.html#error + */ + error(err: any): void + } - /** - * Get the test `err`. - * - * @see https://mochajs.org/api/Hook.html#error - */ - error(): any; + /** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @see https://mochajs.org/api/Runnable.html + */ + class Runnable { + private _slow + private _enableTimeouts + private _retries + private _currentRetry + private _timeout + private _timeoutError + + constructor(title: string, fn?: Func | AsyncFunc) + + title: string + fn: Func | AsyncFunc | undefined + body: string + async: boolean + sync: boolean + timedOut: boolean + pending: boolean + duration?: number + parent?: Suite + state?: 'failed' | 'passed' + timer?: any + ctx?: Context + callback?: Done + allowUncaught?: boolean + file?: string - /** - * Set the test `err`. - * - * @see https://mochajs.org/api/Hook.html#error - */ - error(err: any): void; - } + /** + * Get test timeout. + * + * @see https://mochajs.org/api/Runnable.html#timeout + */ + timeout(): number + + /** + * Set test timeout. + * + * @see https://mochajs.org/api/Runnable.html#timeout + */ + timeout(ms: string | number): this + + /** + * Get test slowness threshold. + * + * @see https://mochajs.org/api/Runnable.html#slow + */ + slow(): number + + /** + * Set test slowness threshold. + * + * @see https://mochajs.org/api/Runnable.html#slow + */ + slow(ms: string | number): this + + /** + * Get whether timeouts are enabled. + * + * @see https://mochajs.org/api/Runnable.html#enableTimeouts + */ + enableTimeouts(): boolean + + /** + * Set whether timeouts are enabled. + * + * @see https://mochajs.org/api/Runnable.html#enableTimeouts + */ + enableTimeouts(enabled: boolean): this + + /** + * Halt and mark as pending. + */ + skip(): never + + /** + * Check if this runnable or its parent suite is marked as pending. + * + * @see https://mochajs.org/api/Runnable.html#isPending + */ + isPending(): boolean + + /** + * Return `true` if this Runnable has failed. + */ + isFailed(): boolean + + /** + * Return `true` if this Runnable has passed. + */ + isPassed(): boolean + + /** + * Set or get number of retries. + * + * @see https://mochajs.org/api/Runnable.html#retries + */ + retries(): number + + /** + * Set or get number of retries. + * + * @see https://mochajs.org/api/Runnable.html#retries + */ + retries(n: number): void + + /** + * Set or get current retry + * + * @see https://mochajs.org/api/Runnable.html#currentRetry + */ + protected currentRetry(): number + + /** + * Set or get current retry + * + * @see https://mochajs.org/api/Runnable.html#currentRetry + */ + protected currentRetry(n: number): void + + /** + * Return the full title generated by recursively concatenating the parent's full title. + */ + fullTitle(): string + + /** + * Return the title path generated by concatenating the parent's title path with the title. + */ + titlePath(): string[] + + /** + * Clear the timeout. + * + * @see https://mochajs.org/api/Runnable.html#clearTimeout + */ + clearTimeout(): void + + /** + * Inspect the runnable void of private properties. + * + * @see https://mochajs.org/api/Runnable.html#inspect + */ + inspect(): string + + /** + * Reset the timeout. + * + * @see https://mochajs.org/api/Runnable.html#resetTimeout + */ + resetTimeout(): void - /** - * Initialize a new `Runnable` with the given `title` and callback `fn`. - * - * @see https://mochajs.org/api/Runnable.html - */ - class Runnable { - private _slow; - private _enableTimeouts; - private _retries; - private _currentRetry; - private _timeout; - private _timeoutError; - - constructor(title: string, fn?: Func | AsyncFunc); - - title: string; - fn: Func | AsyncFunc | undefined; - body: string; - async: boolean; - sync: boolean; - timedOut: boolean; - pending: boolean; - duration?: number; - parent?: Suite; - state?: "failed" | "passed"; - timer?: any; - ctx?: Context; - callback?: Done; - allowUncaught?: boolean; - file?: string; - - /** - * Get test timeout. - * - * @see https://mochajs.org/api/Runnable.html#timeout - */ - timeout(): number; - - /** - * Set test timeout. - * - * @see https://mochajs.org/api/Runnable.html#timeout - */ - timeout(ms: string | number): this; - - /** - * Get test slowness threshold. - * - * @see https://mochajs.org/api/Runnable.html#slow - */ - slow(): number; - - /** - * Set test slowness threshold. - * - * @see https://mochajs.org/api/Runnable.html#slow - */ - slow(ms: string | number): this; - - /** - * Get whether timeouts are enabled. - * - * @see https://mochajs.org/api/Runnable.html#enableTimeouts - */ - enableTimeouts(): boolean; - - /** - * Set whether timeouts are enabled. - * - * @see https://mochajs.org/api/Runnable.html#enableTimeouts - */ - enableTimeouts(enabled: boolean): this; - - /** - * Halt and mark as pending. - */ - skip(): never; - - /** - * Check if this runnable or its parent suite is marked as pending. - * - * @see https://mochajs.org/api/Runnable.html#isPending - */ - isPending(): boolean; - - /** - * Return `true` if this Runnable has failed. - */ - isFailed(): boolean; - - /** - * Return `true` if this Runnable has passed. - */ - isPassed(): boolean; - - /** - * Set or get number of retries. - * - * @see https://mochajs.org/api/Runnable.html#retries - */ - retries(): number; - - /** - * Set or get number of retries. - * - * @see https://mochajs.org/api/Runnable.html#retries - */ - retries(n: number): void; - - /** - * Set or get current retry - * - * @see https://mochajs.org/api/Runnable.html#currentRetry - */ - protected currentRetry(): number; - - /** - * Set or get current retry - * - * @see https://mochajs.org/api/Runnable.html#currentRetry - */ - protected currentRetry(n: number): void; - - /** - * Return the full title generated by recursively concatenating the parent's full title. - */ - fullTitle(): string; - - /** - * Return the title path generated by concatenating the parent's title path with the title. - */ - titlePath(): string[]; - - /** - * Clear the timeout. - * - * @see https://mochajs.org/api/Runnable.html#clearTimeout - */ - clearTimeout(): void; - - /** - * Inspect the runnable void of private properties. - * - * @see https://mochajs.org/api/Runnable.html#inspect - */ - inspect(): string; - - /** - * Reset the timeout. - * - * @see https://mochajs.org/api/Runnable.html#resetTimeout - */ - resetTimeout(): void; - - /** - * Get a list of whitelisted globals for this test run. - * - * @see https://mochajs.org/api/Runnable.html#globals - */ - globals(): string[]; - - /** - * Set a list of whitelisted globals for this test run. - * - * @see https://mochajs.org/api/Runnable.html#globals - */ - globals(globals: ReadonlyArray): void; - - /** - * Run the test and invoke `fn(err)`. - * - * @see https://mochajs.org/api/Runnable.html#run - */ - run(fn: Done): void; + /** + * Get a list of whitelisted globals for this test run. + * + * @see https://mochajs.org/api/Runnable.html#globals + */ + globals(): string[] + + /** + * Set a list of whitelisted globals for this test run. + * + * @see https://mochajs.org/api/Runnable.html#globals + */ + globals(globals: ReadonlyArray): void + + /** + * Run the test and invoke `fn(err)`. + * + * @see https://mochajs.org/api/Runnable.html#run + */ + run(fn: Done): void } - type Done = (err?: any) => void; + type Done = (err?: any) => void /** * Callback function used for tests and hooks. */ - type Func = (this: Context, done: Done) => void; + type Func = (this: Context, done: Done) => void /** * Async callback function used for tests and hooks. */ - type AsyncFunc = (this: Context) => PromiseLike; + type AsyncFunc = (this: Context) => PromiseLike /** - * Test context - * - * @see https://mochajs.org/api/module-Context.html#~Context - */ - class Context { - private _runnable; - - test?: Runnable; - currentTest?: Test; - - /** - * Get the context `Runnable`. - */ - runnable(): Runnable; - - /** - * Set the context `Runnable`. - */ - runnable(runnable: Runnable): this; - - /** - * Get test timeout. - */ - timeout(): number; - - /** - * Set test timeout. - */ - timeout(ms: string | number): this; - - /** - * Get whether timeouts are enabled. - */ - enableTimeouts(): boolean; - - /** - * Set whether timeouts are enabled. - */ - enableTimeouts(enabled: boolean): this; - - /** - * Get test slowness threshold. - */ - slow(): number; - - /** - * Set test slowness threshold. - */ - slow(ms: string | number): this; - - /** - * Mark a test as skipped. - */ - skip(): never; - - /** - * Get the number of allowed retries on failed tests. - */ - retries(): number; - - /** - * Set the number of allowed retries on failed tests. - */ - retries(n: number): this; - - [key: string]: any; + * Test context + * + * @see https://mochajs.org/api/module-Context.html#~Context + */ + class Context { + private _runnable + + test?: Runnable + currentTest?: Test + + /** + * Get the context `Runnable`. + */ + runnable(): Runnable + + /** + * Set the context `Runnable`. + */ + runnable(runnable: Runnable): this + + /** + * Get test timeout. + */ + timeout(): number + + /** + * Set test timeout. + */ + timeout(ms: string | number): this + + /** + * Get whether timeouts are enabled. + */ + enableTimeouts(): boolean + + /** + * Set whether timeouts are enabled. + */ + enableTimeouts(enabled: boolean): this + + /** + * Get test slowness threshold. + */ + slow(): number + + /** + * Set test slowness threshold. + */ + slow(ms: string | number): this + + /** + * Mark a test as skipped. + */ + skip(): never + + /** + * Get the number of allowed retries on failed tests. + */ + retries(): number + + /** + * Set the number of allowed retries on failed tests. + */ + retries(n: number): this + + [key: string]: any } } diff --git a/typings/README.md b/typings/README.md new file mode 100644 index 000000000..b24d136cd --- /dev/null +++ b/typings/README.md @@ -0,0 +1,64 @@ +# TypeScript Type Definitions + +This directory contains TypeScript type definitions (`.d.ts` files) generated from JSDoc comments in the JavaScript source files. + +## Migration from tsd-jsdoc to TypeScript Compiler + +As of version 4.x, CodeceptJS has migrated from using `tsd-jsdoc` to using the TypeScript compiler (`tsc`) directly for generating type definitions. This change was necessary because: + +1. **tsd-jsdoc is no longer maintained** - The last update was over 2 years ago +2. **Peer dependency conflict** - tsd-jsdoc requires JSDoc 3.x, but we need JSDoc 4.x +3. **TypeScript compiler is the modern standard** - Using `tsc --declaration --allowJs` is the recommended approach by Microsoft + +## How It Works + +The type generation process consists of: + +1. **tsconfig.typings.json** - TypeScript configuration for declaration generation +2. **generate-dts.mjs** - Post-processing script that: + - Wraps all types in the `CodeceptJS` namespace (replaces `jsdoc.namespace.cjs`) + - Transforms helper methods to return `Promise` for promise-based helpers (replaces `jsdoc.promiseBased.cjs`) + +## Generating Type Definitions + +To generate type definitions, run: + +```bash +npm run def +``` + +Or manually: + +```bash +# Generate regular type definitions +node typings/generate-dts.mjs tsconfig.typings.json + +# Generate promise-based type definitions +node typings/generate-dts.mjs tsconfig.typings.json --promise-based +``` + +## Files + +- **index.d.ts** - Main type definitions file (hand-written) +- **Mocha.d.ts** - Mocha interface extensions (hand-written) +- **utils.d.ts** - Utility type definitions (hand-written) +- **promiseBasedTypes.d.ts** - Generated promise-based helper type definitions +- **types.d.ts** - Generated regular type definitions +- All other `.d.ts` files are generated from JavaScript source files + +## Custom JSDoc Plugins (Deprecated) + +The following JSDoc plugins are no longer used but kept for reference: + +- **jsdoc.namespace.cjs** - Wrapped types in CodeceptJS namespace (functionality moved to generate-dts.mjs) +- **jsdoc.promiseBased.cjs** - Made helper methods return Promise (functionality moved to generate-dts.mjs) + +These plugins only work with tsd-jsdoc template and are not compatible with JSDoc 4.x. + +## Benefits of the New Approach + +1. **No abandoned dependencies** - TypeScript is actively maintained by Microsoft +2. **Better JSDoc support** - TypeScript has excellent JSDoc parsing +3. **No peer dependency conflicts** - Works with JSDoc 4.x +4. **Simpler toolchain** - One less tool to manage +5. **More accurate types** - TypeScript's inference is superior to tsd-jsdoc diff --git a/typings/generate-dts.mjs b/typings/generate-dts.mjs new file mode 100644 index 000000000..39c50e922 --- /dev/null +++ b/typings/generate-dts.mjs @@ -0,0 +1,266 @@ +#!/usr/bin/env node +/** + * Generate TypeScript type definitions from JavaScript files with JSDoc comments + * + * This replaces tsd-jsdoc by using TypeScript's built-in declaration generation (tsc --declaration --allowJs). + * It replicates the functionality of the custom JSDoc plugins: + * - jsdoc.namespace.cjs: Wraps all types in CodeceptJS namespace + * - jsdoc.promiseBased.cjs: Makes helper methods return Promise + */ + +import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs' +import { join, basename, dirname, extname } from 'path' +import { execSync } from 'child_process' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +/** + * Run TypeScript compiler to generate .d.ts files + * @param {string} configPath - Path to tsconfig + */ +function runTypeScriptCompiler(configPath) { + console.log('Running TypeScript compiler to generate declarations...') + try { + execSync(`npx tsc -p "${configPath}"`, { + cwd: join(__dirname, '..'), + stdio: 'inherit', + }) + } catch (error) { + console.warn('TypeScript compiler encountered errors, but may have generated partial output') + // Don't exit, continue to post-process what was generated + } +} + +/** + * Post-process a .d.ts file to clean up exports + * @param {string} filePath - Path to .d.ts file + */ +function cleanupDeclaration(filePath) { + if (!existsSync(filePath)) { + return + } + + let content = readFileSync(filePath, 'utf-8') + + // Skip processing if already clean + if (content.includes('declare namespace CodeceptJS')) { + return + } + + // Remove problematic export statements that cause module format errors + content = content.replace(/^export\s*=\s*.+;?\s*$/gm, '') + + // Remove empty lines at the start + content = content.replace(/^\s*\n/gm, '') + + writeFileSync(filePath, content) +} + +/** + * Transform helper methods to return Promise for promise-based typings + * @param {string} filePath - Path to .d.ts file + */ +function makePromiseBased(filePath) { + if (!existsSync(filePath)) { + return + } + + let content = readFileSync(filePath, 'utf-8') + + // Rename helper classes to add 'Ts' suffix (e.g., Playwright -> PlaywrightTs) + content = content.replace(/class\s+(\w+)\s+extends/g, 'class $1Ts extends') + content = content.replace(/@augments\s+(\w+)/g, '@augments $1Ts') + + // Transform method signatures to return Promise + // Match: methodName(...): ReturnType + // But skip if already returns Promise + content = content.replace(/^(\s+)(\w+)\s*\(([^)]*)\)\s*:\s*(?!Promise|void)([^;{]+)(;|{)/gm, '$1$2($3): Promise$5') + + // Transform void returns to Promise + content = content.replace(/^(\s+)(\w+)\s*\(([^)]*)\)\s*:\s*void(;|{)/gm, '$1$2($3): Promise$4') + + writeFileSync(filePath, content) +} + +/** + * Get all .d.ts files in a directory recursively + * @param {string} dir - Directory path + * @returns {string[]} Array of file paths + */ +function getAllDtsFiles(dir) { + const files = [] + + if (!existsSync(dir)) { + return files + } + + const items = readdirSync(dir) + + for (const item of items) { + const fullPath = join(dir, item) + const stat = statSync(fullPath) + + if (stat.isDirectory()) { + files.push(...getAllDtsFiles(fullPath)) + } else if (extname(item) === '.ts' && item.endsWith('.d.ts')) { + files.push(fullPath) + } + } + + return files +} + +/** + * Merge multiple .d.ts files into one + * @param {string[]} files - Array of .d.ts file paths + * @param {string} outputPath - Output file path + */ +function mergeDeclarations(files, outputPath) { + console.log(`Merging ${files.length} declaration files...`) + + const merged = [] + const seenDeclarations = new Set() + + for (const file of files) { + if (!existsSync(file)) continue + + const content = readFileSync(file, 'utf-8') + const lines = content.split('\n') + + for (const line of lines) { + const trimmed = line.trim() + + // Skip empty lines, imports, and export statements at top level + if (!trimmed || trimmed.startsWith('import ') || trimmed === 'export {};') { + continue + } + + // Add unique declarations only + if (!seenDeclarations.has(trimmed)) { + merged.push(line) + seenDeclarations.add(trimmed) + } + } + } + + writeFileSync(outputPath, merged.join('\n')) +} + +/** + * Main function + */ +function main() { + const args = process.argv.slice(2) + const isPromiseBased = args.includes('--promise-based') + const configPath = args.find(arg => arg.endsWith('.json')) || 'tsconfig.typings.json' + + const fullConfigPath = join(__dirname, configPath) + + if (!existsSync(fullConfigPath)) { + console.error(`Config file not found: ${fullConfigPath}`) + process.exit(1) + } + + // Step 1: Run TypeScript compiler + runTypeScriptCompiler(fullConfigPath) + + // Step 2: Post-process generated files + console.log('Post-processing generated declarations...') + + const typingsDir = __dirname + const dtsFiles = getAllDtsFiles(typingsDir) + + console.log(`Found ${dtsFiles.length} .d.ts files to process`) + + for (const file of dtsFiles) { + const filename = basename(file) + + // Skip files that are already hand-written + if (filename === 'index.d.ts' || filename === 'Mocha.d.ts' || filename === 'utils.d.ts') { + console.log(`Skipping ${filename} (hand-written)`) + continue + } + + console.log(`Processing ${filename}...`) + + if (isPromiseBased) { + makePromiseBased(file) + } + + cleanupDeclaration(file) + } + + // Step 3: Create consolidated types.d.ts file in CodeceptJS namespace + console.log('\nCreating consolidated types file...') + const outputFilename = isPromiseBased ? 'promiseBasedTypes.d.ts' : 'types.d.ts' + const outputPath = join(typingsDir, outputFilename) + + const consolidated = [] + consolidated.push('// Auto-generated TypeScript definitions') + consolidated.push('// Generated from JSDoc comments using TypeScript compiler') + consolidated.push('') + consolidated.push('declare namespace CodeceptJS {') + + // Collect all class, interface, and type definitions from helper and lib files + const helperFiles = dtsFiles.filter(f => f.includes('/docs/build/')) + const libFiles = dtsFiles.filter( + f => + f.includes('/lib/') && + !f.includes('/lib/command/') && + !f.includes('/lib/listener/') && + !f.includes('/lib/assert/') && + !f.includes('/lib/data/') && + !f.includes('/lib/plugin/') && + !f.includes('/lib/template/') && + !f.includes('/lib/utils/'), + ) + + // For promise-based types, only include helpers. For regular types, include both. + const allFiles = isPromiseBased ? helperFiles : [...libFiles, ...helperFiles] + + for (const file of allFiles) { + if (!existsSync(file)) continue + + let content = readFileSync(file, 'utf-8') + + // Remove all import statements + content = content.replace(/^import .+$/gm, '') + + // Remove export default and export = statements + content = content.replace(/^export default .+$/gm, '') + content = content.replace(/^export = .+$/gm, '') + content = content.replace(/^export \{\};?$/gm, '') + + // Remove export keywords but keep declarations + content = content.replace(/^export (declare )?/gm, '') + content = content.replace(/^declare /gm, '') + + // Keep CodeceptJS. prefix - it's needed for cross-references within the namespace + + // Split into lines and indent + const lines = content.split('\n') + for (const line of lines) { + if (line.trim()) { + consolidated.push(' ' + line) + } else if (consolidated[consolidated.length - 1] !== '') { + // Only add empty line if previous line wasn't empty + consolidated.push('') + } + } + } + + consolidated.push('}') + consolidated.push('') + + writeFileSync(outputPath, consolidated.join('\n')) + console.log(`Created ${outputFilename} with ${allFiles.length} type definitions (${helperFiles.length} helpers, ${libFiles.length} lib)`) + + console.log('\nType definitions generated successfully!') + console.log('Generated .d.ts files are in typings/ directory (excluded from git)') +} + +// Run if executed directly +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main() +} diff --git a/typings/index.d.ts b/typings/index.d.ts index aba11e7a2..4a65a4827 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,6 +1,6 @@ // Project: https://github.com/codeception/codeceptjs/ +// TypeScript definitions generated from JSDoc comments /// -/// /// /// /// diff --git a/typings/translations/index.d.ts b/typings/translations/index.d.ts new file mode 100644 index 000000000..7fa29e56a --- /dev/null +++ b/typings/translations/index.d.ts @@ -0,0 +1,661 @@ +declare const _default: { + 'de-DE': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForEnabled: string + waitForInvisible: string + waitInUrl: string + waitForText: string + moveTo: string + refresh: string + refreshPage: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + haveHeader: string + clearField: string + dontSeeElementInDOM: string + moveCursorTo: string + scrollTo: string + sendGetRequest: string + sendPutRequest: string + sendDeleteRequest: string + sendDeleteRequestWithPayload: string + sendPostRequest: string + switchTo: string + } + } + 'it-IT': { + I: string + contexts: { + Before: string + After: string + BeforeSuite: string + AfterSuite: string + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'fr-FR': { + I: string + contexts: { + Before: string + After: string + BeforeSuite: string + AfterSuite: string + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForEnabled: string + waitForInvisible: string + waitInUrl: string + waitForText: string + moveTo: string + refresh: string + refreshPage: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + clearField: string + dontSeeElementInDOM: string + moveCursorTo: string + scrollTo: string + sendGetRequest: string + sendPutRequest: string + sendDeleteRequest: string + sendPostRequest: string + } + } + 'ja-JP': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'pl-PL': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + haveHeader: string + clearField: string + dontSeeElementInDOM: string + moveCursorTo: string + scrollTo: string + } + } + 'pt-BR': { + I: string + contexts: { + Before: string + After: string + BeforeSuite: string + AfterSuite: string + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + uncheckOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'ru-RU': { + I: string + contexts: { + Before: string + After: string + BeforeSuite: string + AfterSuite: string + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + say: string + waitForElement: string + waitForVisible: string + waitForText: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'zh-CN': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'zh-TW': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForText: string + moveTo: string + refresh: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + } + } + 'nl-NL': { + I: string + contexts: { + Feature: string + Scenario: string + ScenarioOutline: string + } + actions: { + amOutsideAngularApp: string + amInsideAngularApp: string + waitForElement: string + waitForClickable: string + waitForVisible: string + waitForEnabled: string + waitForInvisible: string + waitInUrl: string + waitForText: string + moveTo: string + refresh: string + refreshPage: string + haveModule: string + resetModule: string + amOnPage: string + click: string + doubleClick: string + see: string + dontSee: string + selectOption: string + fillField: string + pressKey: string + triggerMouseEvent: string + attachFile: string + seeInField: string + dontSeeInField: string + appendField: string + checkOption: string + seeCheckboxIsChecked: string + dontSeeCheckboxIsChecked: string + grabTextFrom: string + grabValueFrom: string + grabAttributeFrom: string + seeInTitle: string + dontSeeInTitle: string + grabTitle: string + seeElement: string + dontSeeElement: string + seeInSource: string + dontSeeInSource: string + executeScript: string + executeAsyncScript: string + seeInCurrentUrl: string + dontSeeInCurrentUrl: string + seeCurrentUrlEquals: string + dontSeeCurrentUrlEquals: string + saveScreenshot: string + setCookie: string + clearCookie: string + seeCookie: string + dontSeeCookie: string + grabCookie: string + resizeWindow: string + wait: string + haveHeader: string + clearField: string + dontSeeElementInDOM: string + moveCursorTo: string + scrollTo: string + sendGetRequest: string + sendPutRequest: string + sendDeleteRequest: string + sendDeleteRequestWithPayload: string + sendPostRequest: string + switchTo: string + } + } +} +export default _default diff --git a/typings/translations/utils.d.ts b/typings/translations/utils.d.ts new file mode 100644 index 000000000..ad85303de --- /dev/null +++ b/typings/translations/utils.d.ts @@ -0,0 +1,5 @@ +export function gherkinTranslations(langCode: any): { + Feature: string + Scenario: string + ScenarioOutline: string +} diff --git a/typings/tsconfig.typings.json b/typings/tsconfig.typings.json new file mode 100644 index 000000000..a0f76ad1c --- /dev/null +++ b/typings/tsconfig.typings.json @@ -0,0 +1,51 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": false, + "emitDeclarationOnly": true, + "allowJs": true, + "checkJs": false, + "outDir": "./", + "rootDir": "../", + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "strict": false, + "noEmit": false + }, + "include": [ + "../docs/build/**/*.js", + "../lib/actor.js", + "../lib/codecept.js", + "../lib/config.js", + "../lib/result.js", + "../lib/container.js", + "../lib/data/table.js", + "../lib/data/dataTableArgument.js", + "../lib/effects.js", + "../lib/event.js", + "../lib/index.js", + "../lib/locator.js", + "../lib/output.js", + "../lib/pause.js", + "../lib/recorder.js", + "../lib/secret.js", + "../lib/session.js", + "../lib/step/config.js", + "../lib/step/base.js", + "../lib/step/helper.js", + "../lib/step/meta.js", + "../lib/store.js", + "../lib/mocha/ui.js", + "../lib/mocha/featureConfig.js", + "../lib/mocha/scenarioConfig.js", + "../lib/mocha/bdd.js", + "../lib/mocha/hooks.js" + ], + "exclude": ["../node_modules", "../test"] +} diff --git a/typings/utils.d.ts b/typings/utils.d.ts index 42e77fb0c..4f05f888f 100644 --- a/typings/utils.d.ts +++ b/typings/utils.d.ts @@ -2,6 +2,8 @@ export type ValueOf = T[keyof T] export type KeyValueTupleToObject = { [K in T[0]]: Extract[1] } -export type Translate> = KeyValueTupleToObject> +export type Translate> = KeyValueTupleToObject< + ValueOf<{ + [K in keyof T]: [K extends keyof M ? M[K] : K, T[K]] + }> +>