π Disallow value exports from the autotests/types directory.
πΌ This rule is enabled in the following configs: π all, β
recommended.
In e2ed projects all reusable (and potentially reusable) types live in the autotests/types
directory, and this directory contains only types. Thanks to this convention every import
from autotests/types is known to be a type import: it never pulls runtime code into a test
and never creates a runtime dependency between test helpers. Together with the complementary
no-type-export-outside-types rule (which forbids type
exports everywhere else) this rule keeps the invariant: a type that is used in more than one
file is declared in autotests/types and is exported only from there.
The rule applies only to files in the autotests/types directory (at any depth) and in the
directories and files listed in the additionalTypesPaths option (autotests/configurator/types
by default). In such files every top-level export must be a type-only export: a type alias, an
interface or a re-export with the type modifier. Any other export β a constant, a function,
a class, an enum (including const enum), a namespace, a default export or a re-export
without the type modifier β may export a runtime value and is reported.
// autotests/types/search.ts
export const defaultLanguage = 'pt';
export let searchQuery;
export var searchQuery;
export class SearchQuery {}
export abstract class BasePage {}
// Re-exports without the `type` modifier
export {Product} from './product';
export * from './product';
export * as product from './product';
// Default exports
export default 42;
export default function () {}
export default class {}
// Default exports are reported even for interfaces
export default interface Product {}
// `export import` alias may export a value
export import ProductId = Entities.ProductId;
// Inline `type` modifiers are not enough for re-exports either
export {type Product} from './product';
// An empty `export {}` is reported as well: use `export type {}`
export {};// autotests/types/search.ts
export function getProductId(product: Product): ProductId {
return product.id;
}// autotests/types/search.ts
export class SearchQuery {
limit = 10;
}// autotests/types/search.ts
// `enum` produces a runtime value even in a types file
export enum Language {
PT_BR = 'pt-BR',
}// autotests/types/search.ts
export const enum Currency {
BRL = 'BRL',
}// autotests/types/search.ts
// A `namespace` with value exports
export namespace Search {
export const limit = 10;
}// autotests/types/search.ts
// Exporting a value declared in the file
const defaultLanguage = 'pt';
export {defaultLanguage};// autotests/types/search.ts
// A value export next to a type export is still reported
export type Language = 'pt' | 'en';
export const defaultLanguage: Language = 'pt';// autotests/types/search.ts
// Default export of a type alias is reported like any other default export
type ProductId = string;
export default ProductId;// autotests/types/search.ts
// The `type` modifier must be on the `export` keyword, not on the specifiers
type ProductId = string;
export {type ProductId};// autotests/types/search.ts
// Mixed type and value specifiers
type ProductId = string;
const searchLimit = 10;
export {type ProductId, searchLimit};// autotests/configurator/types/pack.ts
// Value exports in the directories and files from the `additionalTypesPaths` option (`autotests/configurator/types` by default) are reported too
export const pack: Pack = {testIdleTimeout: 10_000};// autotests/configurator/types/qase/index.ts
// At any depth inside such a directory
export {pack} from './pack';// autotests/configurator/types.ts
// A path from the option matches not only a directory, but also a file with this path and any extension
export const pack: Pack = {testIdleTimeout: 10_000};// autotests/hooks/types.ts
// With `additionalTypesPaths: ["autotests/hooks/types"]` the file is checked
export const hooksContext = {startedAt: Date.now()};
// With `additionalTypesPaths: ["autotests/hooks/types.ts"]` the exact file path with the extension is checked
export const hooksContext = {startedAt: Date.now()};// autotests/hooks/context.ts
// With `additionalTypesPaths: ["autotests/configurator/types", "autotests/hooks"]` several paths are checked
export const hooksContext = {startedAt: Date.now()};// autotests/types/search.ts
export type ProductId = Brand<string, 'ProductId'>;
export type {ProductId as Id, Product} from './product';
export type * as Product from './product';
export type * from './product';
// An empty type-only export marks the file as a module
export type {};
// Ambient declarations exist only at the type level
export declare const searchLimit: number;
export declare function getProductId(product: Product): ProductId;
export declare class SearchQuery {}
// Statements without `export` are not checked
const searchLimit = 10;
import {searchLimit} from 'autotests/constants';// autotests/types/search.ts
export interface Product {
id: ProductId;
}// autotests/types/search.ts
type ProductId = Brand<string, 'ProductId'>;
export type {ProductId};// autotests/types/search.ts
import type {Product} from './product';
export type SearchResults = readonly Product[];// autotests/types/search.ts
export declare enum Language {
PT_BR = 'pt-BR',
}// autotests/types/search.ts
export declare namespace Search {
const limit: number;
}// autotests/types/search.ts
// Exports inside a `namespace` block are not exports of the file
namespace Search {
export const limit = 10;
}// autotests/types/search.ts
// Exports inside `declare module` are not checked
declare module 'autotests/constants' {
export const searchLimit: number;
}// autotests/types/search.ts
// Exports inside `declare global` are not checked
declare global {
export interface SearchQuery {}
}// autotests/configurator/types/pack.ts
// The directories and files from the `additionalTypesPaths` option (`autotests/configurator/types` by default) are checked like `autotests/types`
export type Pack = Readonly<{
testIdleTimeout: number;
}>;// autotests/configurator/types/index.ts
export type {Pack} from './pack';// autotests/configurator/types/pack.ts
// With `additionalTypesPaths: []` the `configurator` types directory is not checked
export const pack: Pack = {testIdleTimeout: 10_000};
// With `additionalTypesPaths: ["autotests/hooks"]` the default directory is not checked anymore: the option replaces the default list
export const pack: Pack = {testIdleTimeout: 10_000};// autotests/configurator/index.ts
// By default only `autotests/configurator/types` is checked, not the whole `configurator` directory
export const pack: Pack = {testIdleTimeout: 10_000};// autotests/configurator/types-old.ts
// The file name without extension must match the path exactly
export const pack: Pack = {testIdleTimeout: 10_000};// autotests/hooks/types.mts
// With `additionalTypesPaths: ["autotests/hooks/types.ts"]` only the file with exactly this name is checked
export const hooksContext = {startedAt: Date.now()};// autotests/tests/search/searchPage.ts
// Only files in the `autotests/types` directory are checked
export const searchLimit = 10;// autotests/pageObjects/SearchPage.ts
export const searchLimit = 10;// autotests/pageObjects/types/search.ts
// A `types` directory outside `autotests/types` is not checked
export const searchLimit = 10;// autotests/types-old/search.ts
// The directory must be named exactly `types`
export const searchLimit = 10;// my-autotests/types/search.ts
// The parent directory must be named exactly `autotests`
export const searchLimit = 10;// src/types/search.ts
// Files outside the `autotests` directory are not checked
export const searchLimit = 10;The rule accepts an object with a single property:
additionalTypesPaths(string[], default:["autotests/configurator/types"]) β types directories and files in addition toautotests/types: modules that contain only types, so value exports are reported there as well.autotests/typesitself is always a types directory and cannot be changed or removed, so that every e2ed project has the same structure. Each entry is a normalized path (with/as the separator and without a trailing slash) that is matched as a sequence of whole path segments anywhere in the file path. An entry matches both a directory (all files in it at any depth) and a file with any extension, the same way an import path resolves totypes.tsortypes/index.ts: soautotests/configurator/typesmatches/repo/autotests/configurator/types/pack.ts,autotests/configurator/types.tsandautotests/configurator/types.d.ts, but notautotests/configurator/types-old/pack.tsorautotests/configurator/types-old.ts. An entry with an extension is the exact path of a file:autotests/configurator/types.tsmatches onlyautotests/configurator/types.ts(at any depth, like other entries), but notautotests/configurator/types.mts. The option replaces the default list instead of extending it: includeautotests/configurator/typesexplicitly if it should stay checked, or pass an empty array to checkautotests/typesonly.
{
rules: {
'e2ed/no-value-export-in-types': [
'error',
{additionalTypesPaths: ['autotests/configurator/types', 'autotests/hooks']},
],
},
}The option has the same name, the same default and the same meaning as the option of
no-type-export-outside-types: the types directories and
files are exactly the modules where type exports are allowed and value exports are forbidden.
Keep the two options equal, so that every listed module contains only types and the types are
imported only from there.
autotests/configurator/types is checked by default because of the e2ed contract: e2ed itself
imports the pack types (Pack, TestMeta and others) from autotests/configurator, and
modules of the configurator cannot import anything from other autotests/* directories,
including autotests/types, since the pack config is evaluated before all other code. So the
configurator keeps its own types directory, and it must contain only types, like
autotests/types.
The rule is purely syntactic: it checks the presence of the type modifier and does not
use type information. An export statement without the modifier is reported even if every
exported name happens to be a type (export {type ProductId}, export {Product} where
Product is an interface): use export type {...} instead. An empty export {} is
reported as well β write export type {} to mark the file as a module.
Ambient declarations (export declare const ..., export declare namespace ...) exist
only at the type level and are allowed. Exports inside namespace, declare module and
declare global blocks are not exports of the file and are not checked. CommonJS exports
(module.exports, exports.foo = ...) and the TypeScript-specific export = ... are not
checked either: e2ed autotests are written in ES modules.
The rule enforces a core convention of e2ed and should be enabled in every e2ed project.
The only reason to keep it disabled is a legacy project that still stores enums and
constants in autotests/types and has not yet moved them to autotests/constants.
- e2ed
no-type-export-outside-typesβ the complementary rule- TypeScript: type-only exports